Compare commits

...

46 Commits
main ... dev

Author SHA1 Message Date
12cfa91c6f add 批次管理界面 2025-06-20 17:19:08 +08:00
a4148ff5b2 add 下游上游批次管理 2025-06-20 15:01:07 +08:00
35018c3e3f feat 方案模板 和 项目管理 2025-06-20 13:34:35 +08:00
66a0121ca3 feat 项目负责人加载 2025-06-20 09:39:01 +08:00
c7a5299466 add 项目管理 2025-06-19 20:15:27 +08:00
09eb888093 feat 修复一些bug 2025-06-19 17:54:01 +08:00
74616bcfa6 feat 项目管理 2025-06-18 21:13:05 +08:00
790f31dc1d add 补糖规则 2025-06-18 17:29:53 +08:00
915ca512f5 add 补料任务添加2 2025-06-18 16:48:27 +08:00
1396148d98 add 补料任务添加 2025-06-18 16:48:04 +08:00
9fec9a2edf feat 修改bug 2025-06-18 15:10:48 +08:00
c3f7f719b7 feat 方案模板 基本信息及补料泵修改 2025-06-18 15:02:33 +08:00
acd9fcf076 add 培养方案页面 2025-06-17 23:24:42 +08:00
f42b22dc0a feat:反应器增加相关报警设置 2025-06-17 10:13:46 +08:00
adb93ecad5 feat: 修复一些自测bug 2025-06-16 12:52:13 +08:00
8d95ef8628 feat: 反应器配置编辑页面 2025-06-16 11:42:26 +08:00
bdd217f696 feat 优化反应器列表页面 2025-06-16 09:30:53 +08:00
11aeae4877 feat 优化反应器相关实体 2025-06-13 11:09:28 +08:00
48971c9dc7 feat 反应器页面 2025-06-13 10:02:40 +08:00
49cb6bb23d feat 撤回原因 data-contracts.ts 重构 2025-06-12 18:35:23 +08:00
eb3307594a add 原因管理界面 2025-06-11 19:17:37 +08:00
ff112d066d feat 液袋增加图标显示 2025-06-11 15:24:12 +08:00
e49e4ef1bc add 液袋管理页面 2025-06-11 15:07:39 +08:00
3b6fd601f2 feat emit 2025-06-10 17:36:03 +08:00
ee6ed82a4a add 细胞类型 2025-06-10 14:38:24 +08:00
da8a4ee86e add 补料培养基 2025-06-10 14:16:58 +08:00
8638a7712f add 初始培养基相关功能 2025-06-10 13:45:35 +08:00
d6112be661 feat 调整form 逻辑和样式 2025-06-10 11:50:46 +08:00
5f529f2f06 feat 泵速策略ui美化 2025-06-10 10:30:13 +08:00
9a62ccc99d add 补料策略界面 2025-06-09 19:21:44 +08:00
bd5cbdf5a0 add 继电器Api及前端数据实体 2025-06-09 15:03:23 +08:00
860fd02bbb feat: 修改继电器页面日期选择器样式,与 uspscale 页面保持一致 2025-06-09 15:02:32 +08:00
4018936bf7 feat: 优化打印机和报警器管理功能 - 新增打印机管理功能,优化报警器表单,更新API接口,补充开发文档 2025-06-09 14:14:26 +08:00
1280c11bbc docs: 更新页面开发数据模板,补充路由参数和数据结构说明 2025-06-09 14:13:25 +08:00
7d0b4e76c5 add 报警器相关界面 2025-06-09 11:27:46 +08:00
a671524610 add 空气泵页面但存在打不开弹窗 2025-06-06 17:34:40 +08:00
4aec0f677c add 外置泵的页面 2025-06-06 17:14:54 +08:00
08568085b5 add 设备管理页面 2025-06-06 15:17:12 +08:00
3dbfb97137 feat 四级路由刷新优化 2025-06-06 13:13:07 +08:00
8cc09fa77d feat scale列表美化 2025-06-06 11:39:25 +08:00
c302448bc2 add 地秤管理界面 2025-06-06 11:35:06 +08:00
310af266f9 feat 完善三级路由功能 2025-06-05 18:32:22 +08:00
73e633f686 add bga mock vue 2025-06-05 16:17:25 +08:00
df35c72dae feat 修改三级路由跳转问题 2025-06-05 15:34:51 +08:00
b8c8d403db feat 提交修改 2025-06-05 14:24:36 +08:00
c23a89d222 feat(navMenuHorizontal): add secondary menu navigation and styling fixes; align submenu to left, dropdown to right, and correct icon alignment 2025-06-05 14:21:39 +08:00
85 changed files with 17233 additions and 223 deletions

View File

@ -0,0 +1,63 @@
import { PageInputPumpGetPageInput, PumpGetPageOutput, PumpAddInput, PumpUpdateInput } from './data-contracts'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class AirPumpApi extends HttpClient {
/**
*
*/
getPage = (data: PageInputPumpGetPageInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: PumpAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: PumpUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
}

126
src/api/admin/AlarmApi.ts Normal file
View File

@ -0,0 +1,126 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
AlarmAddInput,
AlarmUpdateInput,
PageInputAlarmGetPageInput,
ResultOutputAlarmGetOutput,
ResultOutputPageOutputAlarmGetPageOutput,
ResultOutputInt64,
} from './data-contracts';
import { ContentType, HttpClient, RequestParams } from './http-client';
import { AxiosResponse } from 'axios';
export class AlarmApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* @description No description
* @tags Alarm -
* @name Get
* @summary
* @request GET:/api/admin/usp-alarm/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number;
},
params: RequestParams = {}
) =>
this.request<ResultOutputAlarmGetOutput, any>({
path: `/api/admin/usp-alarm/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
});
/**
* @description No description
* @tags Alarm -
* @name GetPage
* @summary
* @request POST:/api/admin/usp-alarm/get-page
* @secure
*/
getPage = (data: PageInputAlarmGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputAlarmGetPageOutput, any>({
path: `/api/admin/usp-alarm/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description No description
* @tags Alarm -
* @name Add
* @summary
* @request POST:/api/admin/usp-alarm/add
* @secure
*/
add = (data: AlarmAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/usp-alarm/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
});
/**
* @description No description
* @tags Alarm -
* @name Update
* @summary
* @request PUT:/api/admin/usp-alarm/update
* @secure
*/
update = (data: AlarmUpdateInput, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-alarm/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
});
/**
* @description No description
* @tags Alarm -
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/usp-alarm/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number;
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-alarm/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
});
}

View File

@ -0,0 +1,156 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
EquPrinter,
EquPrinterAddInput,
EquPrinterUpdateInput,
PageInputEquPrinterGetPageInput,
ResultOutputEquPrinter,
ResultOutputPageOutputEquPrinter,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class EquPrinterApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags equ-printer
* @name Get
* @summary
* @request GET:/api/admin/equ-printer/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputEquPrinter, any>({
path: `/api/admin/equ-printer/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags equ-printer
* @name GetPage
* @summary
* @request POST:/api/admin/equ-printer/get-page
* @secure
*/
getPage = (data: PageInputEquPrinterGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputEquPrinter, any>({
path: `/api/admin/equ-printer/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags equ-printer
* @name Add
* @summary
* @request POST:/api/admin/equ-printer/add
* @secure
*/
add = (data: EquPrinterAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/equ-printer/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags equ-printer
* @name Update
* @summary
* @request PUT:/api/admin/equ-printer/update
* @secure
*/
update = (data: EquPrinterUpdateInput, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/equ-printer/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags equ-printer
* @name Delete
* @summary
* @request DELETE:/api/admin/equ-printer/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/equ-printer/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags equ-printer
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/equ-printer/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/equ-printer/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,14 @@
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class FeedingConfigApi extends HttpClient {
/**
*
*/
getList = (params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/usp-feeding-config/get-list`,
method: 'GET',
...params,
})
}

View File

@ -0,0 +1,70 @@
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
import type {
PreBatchDto,
PreBatchPageInput,
PreBatchPageResponse,
PreBatchOutput,
PreBatchAddInput,
PreBatchUpdateInput
} from '/@/api/types/preBatchType'
export class PreBatchApi extends HttpClient {
/**
*
*/
getPage = (data: PreBatchPageInput, params: RequestParams = {}) =>
this.request<PreBatchPageResponse>({
path: `/api/admin/pre-batch/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<PreBatchOutput>({
path: `/api/admin/pre-batch/get`,
method: 'GET',
query: { id: params.id },
...requestParams,
})
/**
*
*/
add = (data: PreBatchAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: PreBatchUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
}

View File

@ -0,0 +1,63 @@
import { ProjectPageInput, ProjectPageResponse, ProjectOutput, ProjectAddInput, ProjectUpdateInput } from '/@/api/types/projectType'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class ProjectApi extends HttpClient {
/**
*
*/
getPage = (data: ProjectPageInput, params: RequestParams = {}) =>
this.request<ProjectPageResponse>({
path: `/api/admin/project/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<ProjectOutput>({
path: `/api/admin/project/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: ProjectAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/project/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: ProjectUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/project/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/project/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
}

View File

@ -0,0 +1,93 @@
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
import type {
ProtocolDto,
ProtocolPageInput,
ProtocolPageResponse,
ProtocolOutput,
ProtocolAddInput,
ProtocolUpdateInput
} from '/@/api/types/protocolType'
export class ProtocolApi extends HttpClient {
/**
*
*/
getPage = (data: ProtocolPageInput, params: RequestParams = {}) =>
this.request<ProtocolPageResponse>({
path: `/api/admin/protocol/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<ProtocolOutput>({
path: `/api/admin/protocol/get`,
method: 'GET',
query: { id: params.id },
...requestParams,
})
/**
*
*/
add = (data: ProtocolAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/protocol/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: ProtocolUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/protocol/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/protocol/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
/**
*
*/
copy = (params: { id: number; reactorId: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/protocol/copy`,
method: 'POST',
body: params,
type: ContentType.Json,
...requestParams,
})
/**
*
*/
toggleStatus = (params: { id: number; isEnabled: boolean }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/protocol/toggle-status`,
method: 'POST',
query: params,
...requestParams,
})
}

63
src/api/admin/PumpApi.ts Normal file
View File

@ -0,0 +1,63 @@
import { PageInputPumpGetPageInput, PumpGetPageOutput, PumpAddInput, PumpUpdateInput } from './data-contracts'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class PumpApi extends HttpClient {
/**
*
*/
getPage = (data: PageInputPumpGetPageInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: PumpAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: PumpUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-pump/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
}

View File

@ -0,0 +1,91 @@
import { ReactorPageInput, ReactorPageResponse, ReactorOutput, ReactorAddInput, ReactorUpdateInput, ReactorTypeEnumListOutput, ReactorListResponse } from '/@/api/types/reactorType'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class ReactorApi extends HttpClient {
/**
*
*/
getPage = (data: ReactorPageInput, params: RequestParams = {}) =>
this.request<ReactorPageResponse>({
path: `/api/admin/equ-reactor/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
getList = (params: RequestParams = {}) =>
this.request<ReactorListResponse>({
path: `/api/admin/equ-reactor/get-list`,
method: 'GET',
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<ReactorOutput>({
path: `/api/admin/equ-reactor/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: ReactorAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-reactor/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: ReactorUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-reactor/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/equ-reactor/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
/**
*
*/
getReactorTypeEnumList = (params: RequestParams = {}) =>
this.request<ReactorTypeEnumListOutput>({
path: `/api/admin/equ-reactor/get-reactor-type-enum-list`,
method: 'GET',
...params,
})
/**
*
*/
getDeviceStatusEnumList = (params: RequestParams = {}) =>
this.request<ReactorTypeEnumListOutput>({
path: `/api/admin/equ-reactor/get-device-status-enum-list`,
method: 'GET',
...params,
})
}

View File

@ -0,0 +1,27 @@
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class ReactorEPSettingApi extends HttpClient {
/**
*
*/
batchUpdate = (data: any[], params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/ep-setting-equ-reactor/batch-update`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
* ID获取报警设置列表
*/
getListByEquReactor = (params: { equReactorId: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/ep-setting-equ-reactor/get-list-by-equ-reactor`,
method: 'GET',
query: params,
...requestParams,
})
}

135
src/api/admin/TestequApi.ts Normal file
View File

@ -0,0 +1,135 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import { ContentType, HttpClient, RequestParams } from './http-client'
import {
TestequGetPageOutput,
PageInputTestequGetPageInput,
ResultOutputPageOutputTestequGetPageOutput,
TestequGetOutput,
ResultOutputTestequGetOutput,
TestequAddInput,
TestequUpdateInput,
ResultOutputString,
ResultOutputInt64
} from './data-contracts'
export class TestequApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* @description
*
* @tags
* @name GetPage
* @summary
* @request POST:/api/admin/testing-equipment/get-page
* @secure
*/
getPage = (data: PageInputTestequGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputTestequGetPageOutput>({
path: `/api/admin/testing-equipment/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* @description
*
* @tags
* @name Get
* @summary
* @request GET:/api/admin/testing-equipment/get
* @secure
*/
get = (
query: {
/** 检测设备ID */
id: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputTestequGetOutput>({
path: `/api/admin/testing-equipment/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* @description
*
* @tags
* @name Add
* @summary
* @request POST:/api/admin/testing-equipment/add
* @secure
*/
add = (data: TestequAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64>({
path: `/api/admin/testing-equipment/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* @description
*
* @tags
* @name Update
* @summary
* @request PUT:/api/admin/testing-equipment/update
* @secure
*/
update = (data: TestequUpdateInput, params: RequestParams = {}) =>
this.request<ResultOutputString>({
path: `/api/admin/testing-equipment/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* @description
*
* @tags
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/testing-equipment/soft-delete
* @secure
*/
softDelete = (
query: {
/** 检测设备ID */
id: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputString>({
path: `/api/admin/testing-equipment/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
format: 'json',
...params,
})
}

View File

@ -0,0 +1,70 @@
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
import type {
UspBatchDto,
UspBatchPageInput,
UspBatchPageResponse,
UspBatchOutput,
UspBatchAddInput,
UspBatchUpdateInput
} from '/@/api/types/uspBatchType'
export class UspBatchApi extends HttpClient {
/**
*
*/
getPage = (data: UspBatchPageInput, params: RequestParams = {}) =>
this.request<UspBatchPageResponse>({
path: `/api/admin/pre-batch/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<UspBatchOutput>({
path: `/api/admin/pre-batch/get`,
method: 'GET',
query: { id: params.id },
...requestParams,
})
/**
*
*/
add = (data: UspBatchAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: UspBatchUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/pre-batch/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
}

View File

@ -0,0 +1,107 @@
import {
PageInputFeedingConfigGetPageInput,
FeedingConfigGetPageOutput,
FeedingConfigUpdateInput,
FeedingConfigAddInput,
FeedingConfigGetOutput,
ResultOutputFeedingConfigGetOutput,
ResultOutputPageOutputFeedingConfigGetPageOutput,
ResultOutputInt64
} from './data-contracts'
import { HttpClient, ContentType, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class UspFeedingConfigApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
*
* @request GET:/api/admin/usp-feeding-config/get
*/
get = (
query?: { id?: number },
params: RequestParams = {}
) =>
this.request<ResultOutputFeedingConfigGetOutput, any>({
path: '/api/admin/usp-feeding-config/get',
method: 'GET',
query,
secure: true,
format: 'json',
...params,
})
/**
*
* @request POST:/api/admin/usp-feeding-config/get-page
*/
getPage = (data: PageInputFeedingConfigGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputFeedingConfigGetPageOutput, any>({
path: '/api/admin/usp-feeding-config/get-page',
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
*
* @request POST:/api/admin/usp-feeding-config/add
*/
add = (data: FeedingConfigAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: '/api/admin/usp-feeding-config/add',
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
*
* @request PUT:/api/admin/usp-feeding-config/update
*/
update = (data: FeedingConfigUpdateInput, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: '/api/admin/usp-feeding-config/update',
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
*
* @request DELETE:/api/admin/usp-feeding-config/delete
*/
delete = (
query?: { id?: number },
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: '/api/admin/usp-feeding-config/delete',
method: 'DELETE',
query,
secure: true,
...params,
})
/**
*
* @request DELETE:/api/admin/usp-feeding-config/soft-delete
*/
softDelete = (
query?: { id?: number },
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: '/api/admin/usp-feeding-config/soft-delete',
method: 'DELETE',
query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,156 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
RelayAddInput,
RelayGetOutput,
RelayUpdateInput,
PageInputRelayGetPageInput,
ResultOutputRelayGetOutput,
ResultOutputPageOutputRelayGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class UspRelayApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags relay
* @name Get
* @summary
* @request GET:/api/admin/usp-relay/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputRelayGetOutput, any>({
path: `/api/admin/usp-relay/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags relay
* @name GetPage
* @summary
* @request POST:/api/admin/usp-relay/get-page
* @secure
*/
getPage = (data: PageInputRelayGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputRelayGetPageOutput, any>({
path: `/api/admin/usp-relay/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags relay
* @name Add
* @summary
* @request POST:/api/admin/usp-relay/add
* @secure
*/
add = (data: RelayAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/usp-relay/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags relay
* @name Update
* @summary
* @request PUT:/api/admin/usp-relay/update
* @secure
*/
update = (data: RelayUpdateInput, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-relay/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags relay
* @name Delete
* @summary
* @request DELETE:/api/admin/usp-relay/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-relay/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags relay
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/usp-relay/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-relay/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,156 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
UspscaleAddInput,
UspscaleGetOutput,
UspscaleUpdateInput,
PageInputUspscaleGetPageInput,
ResultOutputUspscaleGetOutput,
ResultOutputPageOutputUspscaleGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class UspscaleApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags uspscale
* @name Get
* @summary
* @request GET:/api/admin/usp-scale/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputUspscaleGetOutput, any>({
path: `/api/admin/usp-scale/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags uspscale
* @name GetPage
* @summary
* @request POST:/api/admin/usp-scale/get-page
* @secure
*/
getPage = (data: PageInputUspscaleGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputUspscaleGetPageOutput, any>({
path: `/api/admin/usp-scale/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags uspscale
* @name Add
* @summary
* @request POST:/api/admin/usp-scale/add
* @secure
*/
add = (data: UspscaleAddInput, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/usp-scale/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags uspscale
* @name Update
* @summary
* @request PUT:/api/admin/usp-scale/update
* @secure
*/
update = (data: UspscaleUpdateInput, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-scale/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags uspscale
* @name Delete
* @summary
* @request DELETE:/api/admin/usp-scale/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-scale/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags uspscale
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/usp-scale/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/usp-scale/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,12 @@ export const Sex = {
Female: { name: 'Female', value: 2, desc: '女' }, Female: { name: 'Female', value: 2, desc: '女' },
} }
export const Team = {
All: { name: '全部', value: 5, desc: '全部' },
USP: { name: '上游', value: 1, desc: '上游' },
DSP: { name: '下游', value: 3, desc: '下游' },
}
/** 角色类型 */ /** 角色类型 */
export const RoleType = { export const RoleType = {
Group: { name: 'Group', value: 1, desc: '分组' }, Group: { name: 'Group', value: 1, desc: '分组' },
@ -126,3 +132,23 @@ export const AppType = {
MVC: { name: 'MVC', value: 2, desc: '' }, MVC: { name: 'MVC', value: 2, desc: '' },
} }
export const EnumPressureUnit = {
Mpa: { name: 'mbar', value: 'mbar', desc: 'mbar' },
Psi: { name: 'psi', value: 'psi', desc: 'psi' },
Kpa: { name: 'kPa', value: 'kPa', desc: 'kPa' },
}
export const EnumFeedingType = {
Percentage: { name: 'Percentage', value: 1, desc: '百分比' },
Volume: { name: 'Volume', value: 2, desc: '体积' },
}
export const EnumFeedingOperator = {
LessThan: { name: '<', value: 1, desc: '<' },
GreaterThan: { name: '<=', value: 2, desc: '<=' },
}
export const EnumFeedingCalculationMode = {
Add: { name: '残糖', value: 1, desc: '残糖' },
Subtract: { name: '残糖和乳酸', value: 3, desc: '残糖和乳酸' },
}

View File

@ -0,0 +1,155 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
BagDto,
BagModelEnum,
PageInputBagDtoGetPageInput,
ResultOutputBagDtoGetOutput,
ResultOutputPageOutputBagDtoGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class ItemDefBagApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags item-def-bag
* @name Get
* @summary
* @request GET:/api/admin/item-def-bag/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputBagDtoGetOutput, any>({
path: `/api/admin/item-def-bag/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags item-def-bag
* @name GetPage
* @summary
* @request POST:/api/admin/item-def-bag/get-page
* @secure
*/
getPage = (data: PageInputBagDtoGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputBagDtoGetPageOutput, any>({
path: `/api/admin/item-def-bag/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags item-def-bag
* @name Add
* @summary
* @request POST:/api/admin/item-def-bag/add
* @secure
*/
add = (data: BagDto, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/item-def-bag/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags item-def-bag
* @name Update
* @summary
* @request PUT:/api/admin/item-def-bag/update
* @secure
*/
update = (data: BagDto, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-bag/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags item-def-bag
* @name Delete
* @summary
* @request DELETE:/api/admin/item-def-bag/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-bag/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags item-def-bag
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/item-def-bag/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-bag/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,156 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
BasicMediumDto,
BasicMediumDtoListServiceResponse,
BasicMediumDtoServiceResponse,
PageInputBasicMediumDtoGetPageInput,
ResultOutputBasicMediumDtoGetOutput,
ResultOutputPageOutputBasicMediumDtoGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class ItemDefBasicMediumApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags basic-medium
* @name Get
* @summary
* @request GET:/api/admin/item-def-basic-medium/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputBasicMediumDtoGetOutput, any>({
path: `/api/admin/item-def-basic-medium/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags basic-medium
* @name GetPage
* @summary
* @request POST:/api/admin/item-def-basic-medium/get-page
* @secure
*/
getPage = (data: PageInputBasicMediumDtoGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputBasicMediumDtoGetPageOutput, any>({
path: `/api/admin/item-def-basic-medium/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags basic-medium
* @name Add
* @summary
* @request POST:/api/admin/item-def-basic-medium/add
* @secure
*/
add = (data: BasicMediumDto, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/item-def-basic-medium/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags basic-medium
* @name Update
* @summary
* @request PUT:/api/admin/item-def-basic-medium/update
* @secure
*/
update = (data: BasicMediumDto, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-basic-medium/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags basic-medium
* @name Delete
* @summary
* @request DELETE:/api/admin/item-def-basic-medium/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-basic-medium/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags basic-medium
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/item-def-basic-medium/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-basic-medium/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,162 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
CellDto,
CellDtoListServiceResponse,
CellDtoServiceResponse,
PageInputCellDtoGetPageInput,
ResultOutputCellDtoGetOutput,
ResultOutputPageOutputCellDtoGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
/**
*
*/
export class ItemDefCellApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags cell
* @name Get
* @summary
* @request GET:/api/admin/item-def-cell/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<CellDtoServiceResponse, any>({
path: `/api/admin/item-def-cell/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags cell
* @name GetPage
* @summary
* @request POST:/api/admin/item-def-cell/get-page
* @secure
*/
getPage = (data: PageInputCellDtoGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputCellDtoGetPageOutput, any>({
path: `/api/admin/item-def-cell/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags cell
* @name Add
* @summary
* @request POST:/api/admin/item-def-cell/add
* @secure
*/
add = (data: CellDto, params: RequestParams = {}) =>
this.request<CellDtoServiceResponse, any>({
path: `/api/admin/item-def-cell/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags cell
* @name Update
* @summary
* @request PUT:/api/admin/item-def-cell/update
* @secure
*/
update = (data: CellDto, params: RequestParams = {}) =>
this.request<CellDtoServiceResponse, any>({
path: `/api/admin/item-def-cell/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags cell
* @name Delete
* @summary
* @request DELETE:/api/admin/item-def-cell/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<CellDtoServiceResponse, any>({
path: `/api/admin/item-def-cell/delete`,
method: 'DELETE',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags cell
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/item-def-cell/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<CellDtoServiceResponse, any>({
path: `/api/admin/item-def-cell/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
format: 'json',
...params,
})
}

View File

@ -0,0 +1,156 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
FeedMediumDto,
FeedMediumDtoListServiceResponse,
FeedMediumDtoServiceResponse,
PageInputFeedMediumDtoGetPageInput,
ResultOutputFeedMediumDtoGetOutput,
ResultOutputPageOutputFeedMediumDtoGetPageOutput,
ResultOutputInt64,
} from './data-contracts'
import { ContentType, HttpClient, RequestParams } from './http-client'
import { AxiosResponse } from 'axios'
export class ItemDefFeedingMediumApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags feeding-medium
* @name Get
* @summary
* @request GET:/api/admin/item-def-feeding-medium/get
* @secure
*/
get = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<ResultOutputFeedMediumDtoGetOutput, any>({
path: `/api/admin/item-def-feeding-medium/get`,
method: 'GET',
query: query,
secure: true,
format: 'json',
...params,
})
/**
* No description
*
* @tags feeding-medium
* @name GetPage
* @summary
* @request POST:/api/admin/item-def-feeding-medium/get-page
* @secure
*/
getPage = (data: PageInputFeedMediumDtoGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputFeedMediumDtoGetPageOutput, any>({
path: `/api/admin/item-def-feeding-medium/get-page`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags feeding-medium
* @name Add
* @summary
* @request POST:/api/admin/item-def-feeding-medium/add
* @secure
*/
add = (data: FeedMediumDto, params: RequestParams = {}) =>
this.request<ResultOutputInt64, any>({
path: `/api/admin/item-def-feeding-medium/add`,
method: 'POST',
body: data,
secure: true,
type: ContentType.Json,
format: 'json',
...params,
})
/**
* No description
*
* @tags feeding-medium
* @name Update
* @summary
* @request PUT:/api/admin/item-def-feeding-medium/update
* @secure
*/
update = (data: FeedMediumDto, params: RequestParams = {}) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-feeding-medium/update`,
method: 'PUT',
body: data,
secure: true,
type: ContentType.Json,
...params,
})
/**
* No description
*
* @tags feeding-medium
* @name Delete
* @summary
* @request DELETE:/api/admin/item-def-feeding-medium/delete
* @secure
*/
delete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-feeding-medium/delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
/**
* No description
*
* @tags feeding-medium
* @name SoftDelete
* @summary
* @request DELETE:/api/admin/item-def-feeding-medium/soft-delete
* @secure
*/
softDelete = (
query?: {
/** @format int64 */
id?: number
},
params: RequestParams = {}
) =>
this.request<AxiosResponse, any>({
path: `/api/admin/item-def-feeding-medium/soft-delete`,
method: 'DELETE',
query: query,
secure: true,
...params,
})
}

View File

@ -0,0 +1,84 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import { RevokeReasonPageInput, RevokeReasonPageResponse, RevokeReasonOutput, RevokeReasonAddInput, RevokeReasonUpdateInput, RevokeTypeEnumListOutput } from '/@/api/types/RevokeType'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class RevokeReasonApi extends HttpClient {
/**
*
*/
getPage = (data: RevokeReasonPageInput, params: RequestParams = {}) =>
this.request<RevokeReasonPageResponse>({
path: `/api/admin/revoke-reason/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<RevokeReasonOutput>({
path: `/api/admin/revoke-reason/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: RevokeReasonAddInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: RevokeReasonUpdateInput, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
/**
*
*/
getRevokeTypeEnumList = (params: RequestParams = {}) =>
this.request<RevokeTypeEnumListOutput>({
path: `/api/admin/revoke-reason/get-revoke-type-enum-list`,
method: 'GET',
...params,
})
}

View File

@ -0,0 +1,217 @@
import { ServiceResponse } from './response';
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 设备状态枚举
export enum DeviceStatusEnum {
Normal = 0, // 正常
Fault = 1, // 故障
Offline = 2, // 离线
Maintenance = 3 // 维护中
}
// 维护标志枚举
export enum MaintenanceFlagEnum {
None = 0, // 无
Regular = 1, // 定期维护
Emergency = 2 // 紧急维护
}
// 过滤条件
export interface ReactorFilter {
/** 设备编号 */
keyWord?: string | null
/** 开始时间 */
stDate?: string | null
/** 结束时间 */
edDate?: string | null
/** 房间ID */
roomId?: number | null
}
// 反应器DTO
export interface ReactorDto {
/** 设备反应器ID */
id?: number
/** 排序 */
sort?: number
/** 设备编号 */
deviceNo?: string | null
/** 资产编号 */
assetNo?: string | null
/** 产品ID */
productID?: string | null
/** 型号 */
model?: string | null
/** 规格 */
specification?: string | null
/** 容量g */
capacity?: number
/** 房间ID */
roomID?: number
/** 房间名称 */
roomName?: string | null
/** 负责人ID */
principalId?: number
/** 负责人姓名 */
principalName?: string | null
/** 设备状态 */
deviceStatus?: DeviceStatusEnum
/** 设备状态描述 */
deviceStatusDesc?: string | null
/** 维护标志 */
maintenanceFlag?: MaintenanceFlagEnum
/** 维护标志描述 */
maintenanceFlagDesc?: string | null
/** 错误码 */
errorCode?: string | null
/** 状态(启用禁用) */
status?: boolean
/** 是否维护中 */
isMaintenance?: boolean
/** 维护时间 */
maintenanceTime?: string | null
/** 创建时间 */
createdTime?: string | null
/** 修改时间 */
modifiedTime?: string | null
/** 压力单位 */
pressureUnit?: string | null
/** 是否外置泵 */
isExternalPump?: boolean
}
export interface ReactorDtoWithList extends ReactorDto {
pump1Speed?: number
pump2Speed?: number
pump3Speed?: number
pump4Speed?: number
pump5Speed?: number
pump6Speed?: number
pump7Speed?: number
pump8Speed?: number
pump1ConfigId?: number
pump2ConfigId?: number
pump3ConfigId?: number
pump4ConfigId?: number
pump5ConfigId?: number
pump6ConfigId?: number
pump7ConfigId?: number
pump8ConfigId?: number
}
/** 反应器添加和更新输入接口 */
export interface ReactorAddInputAndUpdateInput {
/** 设备编号 */
deviceNo?: string | null
/** 资产编号 */
assetNo?: string | null
/** 产品ID */
productID?: string | null
/** 型号 */
model?: string | null
/** 规格 */
specification?: string | null
/** 容量g */
capacity?: number
/** 房间ID */
roomID?: number
/** 负责人ID */
principalId?: number
/** 设备状态 */
deviceStatus?: DeviceStatusEnum
/** 维护标志 */
maintenanceFlag?: MaintenanceFlagEnum
/** 维护时间 */
maintenanceTime?: string | null
/** 压力单位 */
pressureUnit?: string | null
/** 反应器地秤ID */
equScaleId?: number
/** 补料秤ID */
feedingScaleId?: number
/** 继电器ID */
relayID?: number
/** 报警器ID */
equAlarmId?: number
/** 警告下限 */
warningLowerLimit?: number
/** 警告上限 */
warningUpperLimit?: number
/** 错误码 */
errorCode?: string | null
/** 是否外置泵 */
isExternalPump?: boolean
/** OPC IP */
opcip?: string | null
/** OPC 端口 */
opcPort?: string | null
/** 排序 */
sort?: number
/** 主键Id */
id?: number
/** 泵1配置 */
pump1: PumpConfig
/** 泵2配置 */
pump2: PumpConfig
/** 泵3配置 */
pump3: PumpConfig
/** 泵4配置 */
pump4: PumpConfig
/** 泵5配置 */
pump5: PumpConfig
/** 泵6配置 */
pump6: PumpConfig
/** 泵7配置 */
pump7: PumpConfig
/** 泵8配置 */
pump8: PumpConfig
}
// 反应器类型枚举项
export interface ReactorTypeEnumItem {
/** 枚举值 */
value: number
/** 枚举名称 */
name: string
/** 显示名称/描述 */
label: string
}
/** 泵配置接口 */
export interface PumpConfig {
/** 速度 */
speed?: number
/** 超时时间 */
timeout: number
/** 异常信息 */
exception: string
/** 错误码 */
errorCode: string
/** 是否喂料 */
isFeeding: boolean
/** 速度偏移 */
speedOffset: number
/** 配置ID */
configId?: number
}
/** 默认泵配置 */
export const defaultPumpConfig: PumpConfig = {
speed: undefined,
timeout: 0,
exception: '',
errorCode: '',
isFeeding: true,
speedOffset: 0,
configId: undefined
}
// API 类型定义
export type ReactorPageInput = ServiceRequestPage<ReactorFilter>;
export type ReactorPageResponse = ServiceResponse<PageResponse<ReactorDto>>;
export type ReactorListResponse = ServiceResponse<ReactorDtoWithList[]>;
export type ReactorOutput = ServiceResponse<ReactorDto[]>;
export type ReactorAddInput = ReactorDto;
export type ReactorUpdateInput = ReactorDto;
export type ReactorTypeEnumListOutput = ServiceResponse<ReactorTypeEnumItem[]>;

13
src/api/types/medium.ts Normal file
View File

@ -0,0 +1,13 @@
import { ServiceResponse } from './response';
export interface BasicMediumDto {
mediumName: string;
status: boolean;
id: number;
createdTime: string;
modifiedTime: string;
isDeleted: boolean;
}
export type BasicMediumDtoListServiceResponse = ServiceResponse<BasicMediumDto[]>;
export type BasicMediumDtoServiceResponse = ServiceResponse<BasicMediumDto>;

View File

@ -0,0 +1,34 @@
export interface ServiceRequestPage<T> {
dynamicFilter?: DynamicFilterInfo;
/** 排序列表 */
sortList?: SortInput[] | null;
/**
*
* @format int32
*/
currentPage?: number;
/**
*
* @format int32
*/
pageSize?: number;
/** 分页请求 */
filter?: T | null;
}
/** 排序 */
export interface SortInput {
/** 属性名称 */
propName?: string | null
/** 排序方式:Asc=0,Desc=1 */
order?: SortOrder
/** 是否升序 */
isAscending?: boolean | null
}
/**
* 排序方式:Asc=0,Desc=1
* @format int32
*/
export type SortOrder = 0 | 1

View File

@ -0,0 +1,4 @@
export interface PageResponse<T> {
list?: T[] | null;
total?: number;
}

View File

@ -0,0 +1,64 @@
import { ServiceResponse } from './response'
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤条件
export interface PreBatchFilter {
/** 预批次名称 */
keyWord?: string | null
/** 项目ID */
projectId?: number | null
/** 是否下游 */
isDown?: boolean | null
/** 开始时间 */
stDate?: string | null
/** 结束时间 */
edDate?: string | null
}
/**
*
*/
export interface PreBatchPageDto {
/** 主键ID */
id: number
/** 预批次名称 */
preBatchName: string
/** 预批次编号 */
preBatchNo: string
/** 项目ID */
projectId?: number | null
/** 项目负责人 */
projectUserName?: string
/** 创建用户真实姓名 */
createdUserRealName?: string
/** 创建时间 */
createdTime: string
}
/**
*
*/
export interface PreBatchDto {
/** 主键ID */
id?: number
/** 预批次名称 */
preBatchName: string
/** 预批次编号 */
preBatchNo: string
/** 项目ID */
projectId?: number | null
/** 是否下游 0否1是 */
isDown?: boolean | null
/** 滴度 */
titer?: number | null
projectUserName?: string
}
export type PreBatchPageInput = ServiceRequestPage<PreBatchFilter>
export type PreBatchPageResponse = ServiceResponse<PageResponse<PreBatchPageDto>>
export type PreBatchOutput = ServiceResponse<PreBatchDto>
export type PreBatchAddInput = PreBatchDto
export type PreBatchUpdateInput = PreBatchDto

View File

@ -0,0 +1,75 @@
import { ServiceResponse } from './response';
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
import { ProjectUser, ProjectPrincipal } from './projectUserType'
// 过滤条件
export interface ProjectFilter {
/** 关键字搜索 */
keyWord?: string | null
/** 部门ID */
departmentId?: number | null
}
// 项目DTO
export interface ProjectDto {
/** 主键Id */
id?: number
/** 部门ID */
departmentId?: number
/** 部门名称 */
departmentName?: string | null
/** 项目编号 */
projectNo?: string | null
/** 项目名称 */
projectName?: string | null
/** 责任人ID */
principalId?: number
/** 责任人姓名 */
principalName?: string | null
/** 状态 */
status?: boolean
/** 创建时间 */
createdTime?: string | null
/** 创建人姓名 */
createdUserName?: string | null
/** 修改时间 */
modifiedTime?: string | null
/** 项目成员 */
projectUsers?: ProjectUser[]
/** 项目负责人 */
projectPrincipals?: ProjectPrincipal[]
}
/** 项目添加和更新输入接口 */
export interface ProjectAddInputAndUpdateInput {
/** 主键Id */
id?: number
/** 部门ID */
departmentId?: number
/** 项目编号 */
projectNo?: string | null
/** 项目名称 */
projectName?: string | null
/** 责任人ID */
principalId?: number
/** 状态 */
status?: boolean
/** 项目成员 */
projectUsers?: ProjectUser[]
/** 项目负责人 */
projectPrincipals?: ProjectPrincipal[]
}
// API 类型定义
export type ProjectPageInput = ServiceRequestPage<ProjectFilter>;
export type ProjectPageResponse = ServiceResponse<PageResponse<ProjectDto>>;
export type ProjectOutput = ServiceResponse<ProjectDto>;
export type ProjectAddInput = ProjectAddInputAndUpdateInput;
export type ProjectUpdateInput = ProjectAddInputAndUpdateInput;

View File

@ -0,0 +1,61 @@
/**
*
*/
/**
*
*/
export interface ProjectUser {
/** ID */
id?: number;
/** 项目ID */
projectId?: number;
/** 所属组ID */
groupId?: number;
/** 用户ID */
userId: number;
/** 用户过期时间 */
limitToDate?: string;
/** 上传权限 */
upload: boolean;
/** 撤回上传权限 */
revokeUpload: boolean;
/** 审核权限 */
review: boolean;
/** 撤回审核权限 */
revokeReview: boolean;
/** 签名权限 */
signature: boolean;
/** 撤回签名权限 */
revokeSignature: boolean;
/** 审核(二)权限 */
verify: boolean;
/** 撤回审核(二)权限 */
revokeVerify: boolean;
}
/**
*
*/
export interface ProjectPrincipal {
/** ID */
id?: number;
/** 项目ID */
projectId?: number;
/** 用户ID */
userId: number;
}

View File

@ -0,0 +1,154 @@
import { ServiceResponse } from './response'
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤条件
export interface ProtocolFilter {
/** 设备编号 */
keyWord?: string | null
/** 开始时间 */
stDate?: string | null
/** 结束时间 */
edDate?: string | null
/** 反应器Id */
equReactorId?: number | null
}
/**
*
*/
export interface ProtocolPageDto {
/** 主键ID */
id: number
/** 培养方案名称 */
ProtocolName: string
/** 反应器ID */
equReactorId: number
/** 补料秤ID */
equFeedingScaleId: number
/** 培养天数 */
days: number
/** 初始工作体积 */
initialWorkingVolume: number
/** 初始工作培养基ID */
initialWorkingMediumId: number
/** 转种细胞体积 */
transferCellVolume: number
/** 转种细胞类型ID */
transferCellTypeId: number
/** 实验组ID */
experimentalGroupId: number
/** 葡萄糖补料验证 */
glucoseFeedingVerification: boolean
/** 备注 */
remark: string
/** 是否启用 */
enabled: boolean
/** 创建时间 */
createdTime: string,
/** 补料任务数量 */
feedingTaskCount: number
}
export interface ProtocolDto {
/** 主键ID */
id: number
/** 培养方案名称 */
protocolName: string
/** 反应器ID */
equReactorId?: number
/** 补料秤ID */
equFeedingScaleId?: number
/** 培养天数 */
days: number
/** 初始工作体积 */
initialWorkingVolume: number
/** 初始工作培养基ID */
initialWorkingMediumId: number
/** 转种细胞体积 */
transferCellVolume: number
/** 转种细胞类型ID */
transferCellTypeId: number
/** 葡萄糖补料验证 */
glucoseFeedingVerification: boolean
/** 备注 */
remark: string
/** 自动葡萄糖补料规则 */
autoGlucoseFeedingRules: AutoGlucoseFeedingRule[]
/** 补料任务 */
feedingTasks: FeedingTask[]
/** 固定补料泵配置 */
fixedFeedingPumps: FixedFeedingPump[]
}
/** 自动葡萄糖补料规则 */
export interface AutoGlucoseFeedingRule {
/** 主键ID */
id: number
/** 培养方案ID */
protocolId: number
/** 培养开始天数 */
cultureDayStarting: number
/** 培养结束天数 */
cultureDayEnding: number
/** 下限值 */
lowerLimit: number
/** 补料目标值 */
feedingTo: number
/** 下限操作符 */
lowerLimitOperator: string
/** 计算模式 */
calculationMode: number
}
/** 补料任务 */
export interface FeedingTask {
/** 主键ID */
id: number
/** 培养方案ID */
protocolId: number
/** 补料泵编号 */
feedingPumpNo: number
/** 培养天数 */
day: number
/** 补料时间 */
feedingTime: string
/** 补料类型 */
feedingType: number
/** 补料体积百分比 */
feedingVolumePercent: number
/** 补料体积 */
feedingVolume: number
}
/** 固定补料泵配置 */
export interface FixedFeedingPump {
/** 主键ID */
id: number
/** 培养方案ID */
protocolId?: number
/** 配置ID */
configId?: number
/** 补料泵编号 */
feedingPumpNo: number
/** 物料定义袋ID */
itemDefBagID?: number
/** 物料定义培养基ID */
itemDefFeedingMediumID?: number
/** 设备泵ID */
equPumpId?: number
/** 补料泵名称 */
feedingPumpName?: string
/** 量程(g/min) */
pumpSpeed?: number
/** 是否是糖补料 */
isGlucose?: boolean
}
export type ProtocolPageInput = ServiceRequestPage<ProtocolFilter>
export type ProtocolPageResponse = ServiceResponse<PageResponse<ProtocolPageDto>>;
export type ProtocolOutput = ServiceResponse<ProtocolDto>;
export type ProtocolAddInput = ProtocolDto;
export type ProtocolUpdateInput = ProtocolDto;

View File

@ -0,0 +1,38 @@
// 反应器报警设置类型定义
export interface ReactorEPSetting {
/** 主键ID */
id: number
/** 设备反应器ID */
equReactorId: number
/** EP定义ID */
epDefId: number
/** EP定义 */
epDefName: string
/** EP定义别名 */
epDefAlias: string
/** 告警下限 */
warningLowerLimit: number
/** 告警下限缓冲 */
warningLowerBuffer: number
/** 告警上限 */
warningUpperLimit: number
/** 告警上限缓冲 */
warningUpperBuffer: number
/** 动作下限 */
actionLowerLimit: number
/** 动作上限 */
actionUpperLimit: number
/** 动作时间缓冲 */
actionTimeBuffer: number
/** 预警时间缓冲 */
warningTimeBuffer: number
/** 是否启用 */
isEnable: boolean
/** 状态 */
status: boolean
/** 计量单位 */
unit: string
/** OPC名称 */
needConfig: boolean
}

View File

@ -0,0 +1,6 @@
export interface ServiceResponse<T> {
data?: T | null;
success?: boolean;
code?: string | null;
message?: string | null;
}

104
src/api/types/revokeType.ts Normal file
View File

@ -0,0 +1,104 @@
import { ServiceResponse } from './response';
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤
export interface RevokeReasonFilter {
/** 原因描述 */
keyWord?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
startTime?: string | null
endTime?: string | null
}
export interface RevokeReasonDto {
/**
*
* @format int64
*/
id?: number
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
/** 创建者 */
createdUserName?: string | null
/**
*
* @format date-time
*/
createdTime?: string | null
/**
*
* @format date-time
*/
modifiedTime?: string | null
}
export interface RevokeReasonPageDto {
/**
*
* @format int64
*/
id?: number
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
/** 创建者 */
createdUserName?: string | null
/**
*
* @format date-time
*/
createdTime?: string | null
/**
*
* @format date-time
*/
modifiedTime?: string | null
}
export interface RevokeReasonDto {
/**
*
* @format int64
*/
id?: number
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
}
/** 撤回类型枚举项 */
export interface RevokeTypeEnumItem {
/** 枚举值 */
value: number
/** 枚举名称 */
name: string
/** 显示名称/描述 */
label: string
}
export type RevokeReasonPageInput = ServiceRequestPage<RevokeReasonFilter>;
export type RevokeReasonPageResponse = ServiceResponse<PageResponse<RevokeReasonPageDto>>;
export type RevokeReasonOutput = ServiceResponse<RevokeReasonDto[]>;
export type RevokeReasonAddInput = RevokeReasonDto;
export type RevokeReasonUpdateInput = RevokeReasonDto;
export type RevokeTypeEnumListOutput = ServiceResponse<RevokeTypeEnumItem[]>;

View File

@ -0,0 +1,64 @@
import { ServiceResponse } from './response'
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤条件
export interface UspBatchFilter {
/** 关键字 */
keyWord?: string | null
/** 项目ID */
projectId?: number | null
/** 是否下游 */
isDown?: boolean | null
/** 开始时间 */
stDate?: string | null
/** 结束时间 */
edDate?: string | null
}
/**
* USP批次分页数据接口
*/
export interface UspBatchPageDto {
/** 主键ID */
id: number
/** USP批次名称 */
uspBatchName: string
/** USP批次编号 */
uspBatchNo: string
/** 项目ID */
projectId?: number | null
/** 是否下游 */
isDown?: boolean | null
/** 滴度 */
titer?: number | null
/** 创建用户真实姓名 */
createdUserRealName?: string
/** 创建时间 */
createdTime: string
}
/**
* USP批次完整数据接口
*/
export interface UspBatchDto {
/** 主键ID */
id?: number
/** USP批次名称 */
uspBatchName: string
/** USP批次编号 */
uspBatchNo: string
/** 项目ID */
projectId?: number | null
/** 是否下游 */
isDown?: boolean | null
/** 滴度 */
titer?: number | null
}
export type UspBatchPageInput = ServiceRequestPage<UspBatchFilter>
export type UspBatchPageResponse = ServiceResponse<PageResponse<UspBatchPageDto>>
export type UspBatchOutput = ServiceResponse<UspBatchDto>
export type UspBatchAddInput = UspBatchDto
export type UspBatchUpdateInput = UspBatchDto

View File

@ -82,21 +82,7 @@ const closeLayoutAsideMobileMode = () => {
if (clientWidth < 1000) themeConfig.value.isCollapse = false if (clientWidth < 1000) themeConfig.value.isCollapse = false
document.body.setAttribute('class', '') document.body.setAttribute('class', '')
} }
// //
const setFilterRoutes = () => {
if (themeConfig.value.layout === 'columns') return false
state.menuList = filterRoutesFun(routesList.value)
}
//
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
item = Object.assign({}, item)
if (item.children) item.children = filterRoutesFun(item.children)
return item
})
}
// //
const initMenuFixed = (clientWidth: number) => { const initMenuFixed = (clientWidth: number) => {
state.clientWidth = clientWidth state.clientWidth = clientWidth
@ -112,7 +98,6 @@ const onAsideEnterLeave = (bool: Boolean) => {
// //
onBeforeMount(() => { onBeforeMount(() => {
initMenuFixed(document.body.clientWidth) initMenuFixed(document.body.clientWidth)
setFilterRoutes()
// (mittBus.off('setSendColumnsChildren)) // (mittBus.off('setSendColumnsChildren))
// 使 // 使
mittBus.on('setSendColumnsChildren', (res: MittMenu) => { mittBus.on('setSendColumnsChildren', (res: MittMenu) => {
@ -131,10 +116,6 @@ onBeforeMount(() => {
state.menuList = res.children state.menuList = res.children
} }
}) })
//
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes()
})
// () // ()
mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => { mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => {
initMenuFixed(res.clientWidth) initMenuFixed(res.clientWidth)
@ -157,11 +138,17 @@ watch(
if (layout === 'classic' && isClassicSplitMenu) return false if (layout === 'classic' && isClassicSplitMenu) return false
} }
) )
// ` -> -> ` //
watch( watch(
() => routesList.value, () => routesList.value,
() => { () => {
setFilterRoutes() //
} if (themeConfig.value.layout === 'defaults') return
//
if (routesList.value.length > 0) {
layoutAsideScrollbarRef.value?.update()
}
},
{ deep: true }
) )
</script> </script>

View File

@ -3,8 +3,8 @@
<div class="left-section"> <div class="left-section">
<Logo v-if="setIsShowLogo" /> <Logo v-if="setIsShowLogo" />
</div> </div>
<Horizontal :menuList="state.menuList" />
<div class="right-section"> <div class="right-section">
<Horizontal :menuList="state.menuList" />
<User /> <User />
</div> </div>
</div> </div>
@ -12,11 +12,13 @@
<script setup lang="ts" name="layoutBreadcrumbIndex"> <script setup lang="ts" name="layoutBreadcrumbIndex">
import { defineAsyncComponent, computed, reactive, onMounted, onUnmounted, onBeforeMount } from 'vue' import { defineAsyncComponent, computed, reactive, onMounted, onUnmounted, onBeforeMount } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter, RouteRecordRaw } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useRoutesList } from '/@/stores/routesList' import { useRoutesList } from '/@/stores/routesList'
import { useThemeConfig } from '/@/stores/themeConfig' import { useThemeConfig } from '/@/stores/themeConfig'
import mittBus from '/@/utils/mitt' import mittBus from '/@/utils/mitt'
import { treeToList, listToTree, filterList } from '/@/utils/tree'
import { cloneDeep } from 'lodash-es'
// //
const User = defineAsyncComponent(() => import('/@/layout/navBars/topBar/user.vue')) const User = defineAsyncComponent(() => import('/@/layout/navBars/topBar/user.vue'))
@ -49,11 +51,7 @@ const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = themeConfig.value let { isShowLogo, layout } = themeConfig.value
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse') return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse')
}) })
//
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = themeConfig.value
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic')
})
// // // //
const setFilterRoutes = () => { const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = themeConfig.value let { layout, isClassicSplitMenu } = themeConfig.value
@ -62,18 +60,13 @@ const setFilterRoutes = () => {
// //
state.menuList = filterRoutesFun(routesList.value) // state.menuList = filterRoutesFun(routesList.value) //
const resData = setSendClassicChildren(route.path) const resData = setSendClassicChildren(route.path)
mittBus.emit('setSendClassicChildren', resData) //console.log('/', resData)
//mittBus.emit('setSendClassicChildren', resData)
} else { } else {
state.menuList = filterRoutesFun(routesList.value) state.menuList = filterRoutesFun(routesList.value)
} }
} }
// children
const delClassicChildren = <T extends ChilType>(arr: T[]): T[] => {
arr.map((v: T) => {
if (v.children) delete v.children
})
return arr
}
// //
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => { const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr return arr
@ -85,47 +78,45 @@ const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return item return item
}) })
} }
//
const setSendClassicChildren = (path: string) => { const getRootPath = (path: string) => {
const currentPathSplit = path.split('/') let rootPath = ''
let currentData: MittMenu = { children: [] } let routeTree = listToTree(
filterRoutesFun(routesList.value).map((v: RouteItem, k: number) => { filterList(treeToList(cloneDeep(routesList.value)), path, {
if (v.path === `/${currentPathSplit[1]}`) { filterWhere: (item: any, filterword: string) => {
v['k'] = k return item.path?.toLocaleLowerCase() === filterword
currentData['item'] = { ...v } },
currentData['children'] = [{ ...v }] })
if (v.children) currentData['children'] = v.children )
} if (routeTree.length > 0 && routeTree[0]?.path) {
}) rootPath = routeTree[0].path
return currentData }
return rootPath
} }
// props.menuList fallback layout.children //
const topModules = computed<RouteItem[]>(() => { const setSendClassicChildren = (path: string) => {
if (props.menuList && props.menuList.length) { let res: MittMenu = { children: [] }
return filterRoutesFun(props.menuList)
}
const layout = routesList.value.find((item: RouteItem) => item.path === '/')
if (!layout || !layout.children) return []
return filterRoutesFun(layout.children)
})
const onModuleChange = (path: string) => { //
router.push(path) const rootPath = getRootPath(path)
mittBus.emit('getBreadcrumbIndexSetFilterRoutes') const module = state.menuList.find(item => item.path === rootPath)
if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) { if (module && module.children) {
mittBus.emit('setSendClassicChildren', path) const sub = module.children.find((child: RouteRecordRaw) => path.startsWith(child.path))
if (sub) {
res.item = { ...sub }
res.children = sub.children ? [...sub.children] : []
}
} }
return res
} }
// //
onMounted(() => { onMounted(() => {
setFilterRoutes() setFilterRoutes()
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes()
})
if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) { if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', route.path) mittBus.emit('setSendClassicChildren', setSendClassicChildren(route.path))
} }
}) })
// //
@ -135,7 +126,8 @@ onUnmounted(() => {
onBeforeMount(() => { onBeforeMount(() => {
if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) { if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', route.path) //console.log('onBeforeMount11122', route.path)
//mittBus.emit('setSendClassicChildren', route.path)
} }
}) })

View File

@ -1,15 +1,33 @@
<template> <template>
<div class="el-menu-horizontal-warp"> <div class="el-menu-horizontal-warp">
<!-- 新增二级菜单导航 -->
<el-menu
class="sub-menu"
mode="horizontal"
:default-active="currentSubMenuPath"
background-color="transparent"
@select="onSubMenuClick"
>
<el-menu-item
v-for="item in subMenuList"
:index="item.path"
:key="item.path"
>
<SvgIcon :name="item.meta?.icon" class="submenu-icon" :size="20" />
<span>{{ $t(item.meta?.title) }}</span>
</el-menu-item>
</el-menu>
<!-- 将四个模块合并为一个下拉菜单类似语言切换 --> <!-- 将四个模块合并为一个下拉菜单类似语言切换 -->
<el-dropdown <el-dropdown
:show-timeout="70" :show-timeout="70"
:hide-timeout="50" :hide-timeout="50"
trigger="click" trigger="click"
@command="onModuleCommand" @command="onModuleCommand"
class="module-dropdown" class="module-dropdown"
> >
<div class="module-selector"> <div class="module-selector">
<SvgIcon :name="currentModule.icon" /> <SvgIcon :name="currentModule.icon" :size="20" />
<span class="module-title">{{ currentModule.title }}</span> <span class="module-title">{{ currentModule.title }}</span>
<el-icon class="el-icon--right"> <el-icon class="el-icon--right">
<ele-ArrowDown /> <ele-ArrowDown />
@ -17,8 +35,8 @@
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
v-for="module in moduleList" v-for="module in moduleList"
:key="module.path" :key="module.path"
:command="module.path" :command="module.path"
:class="{ 'is-active': isCurrentModule(module.path) }" :class="{ 'is-active': isCurrentModule(module.path) }"
@ -33,13 +51,12 @@
</template> </template>
<script setup lang="ts" name="navMenuHorizontal"> <script setup lang="ts" name="navMenuHorizontal">
import { defineAsyncComponent, reactive, computed, onBeforeMount } from 'vue' import { reactive, computed, onBeforeMount } from 'vue'
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw, useRouter } from 'vue-router' import { useRoute, onBeforeRouteUpdate, RouteRecordRaw, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoutesList } from '/@/stores/routesList' import { useRoutesList } from '/@/stores/routesList'
import { useThemeConfig } from '/@/stores/themeConfig' import { useThemeConfig } from '/@/stores/themeConfig'
import other from '/@/utils/other'
import mittBus from '/@/utils/mitt' import mittBus from '/@/utils/mitt'
import { treeToList, listToTree, filterList } from '/@/utils/tree' import { treeToList, listToTree, filterList } from '/@/utils/tree'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
@ -47,9 +64,6 @@ import { cloneDeep } from 'lodash-es'
const router = useRouter() const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
//
// const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')) // 使el-dropdown
// //
const props = defineProps({ const props = defineProps({
// //
@ -86,14 +100,15 @@ const moduleList = computed(() => {
// //
const currentModule = computed(() => { const currentModule = computed(() => {
const current = moduleList.value.find(module => const current = moduleList.value.find(module =>
state.currentModulePath === module.path || state.currentModulePath === module.path ||
route.path.startsWith(module.path) route.path.startsWith(module.path)
) )
return current || moduleList.value[0] || {
path: '', return current || moduleList.value[0] || {
title: '选择模块', path: '',
icon: 'ele-Menu' title: '选择模块',
icon: 'ele-Menu'
} }
}) })
// //
@ -154,44 +169,236 @@ const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
else state.defaultActive = path else state.defaultActive = path
} }
} }
//
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val)
}
// //
const onModuleCommand = (path: string) => { const onModuleCommand = (path: string) => {
console.log('🎯 [Horizontal Menu] onModuleCommand - 模块切换:', {
selectedPath: path,
currentModulePath: state.currentModulePath
})
// //
state.currentModulePath = path state.currentModulePath = path
console.log('🎯 [Horizontal Menu] onModuleCommand - 更新当前模块路径:', state.currentModulePath)
// //
const selectedModule = menuLists.value.find(item => item.path === path) const selectedModule = menuLists.value.find(item => item.path === path)
if (!selectedModule) return console.log('🎯 [Horizontal Menu] onModuleCommand - 查找选中模块:', {
selectedModule: selectedModule ? {
// 使 path: selectedModule.path,
if (selectedModule.meta?.isLink && !selectedModule.meta?.isIframe) { title: selectedModule.meta?.title,
onALinkClick(selectedModule) hasChildren: !!selectedModule.children,
return childrenLength: selectedModule.children?.length || 0
} } : null
})
// if (!selectedModule) return
//
if (selectedModule.children && selectedModule.children.length > 0) { if (selectedModule.children && selectedModule.children.length > 0) {
const firstChild = selectedModule.children.find((child: RouteItem) => !child.meta?.isHide) const firstChild = selectedModule.children.find((child: RouteRecordRaw) => !child.meta?.isHide)
console.log('🎯 [Horizontal Menu] onModuleCommand - 查找第一个子菜单:', {
firstChild: firstChild ? {
path: firstChild.path,
title: firstChild.meta?.title,
isHide: firstChild.meta?.isHide
} : null
})
if (firstChild) { if (firstChild) {
console.log('🎯 [Horizontal Menu] onModuleCommand - 跳转到子菜单:', firstChild.path)
router.push(firstChild.path) router.push(firstChild.path)
//
const subMenuData = setSendSubMenuChildren(firstChild.path)
console.log('🎯 [Horizontal Menu] onModuleCommand - 发送子菜单数据:', subMenuData)
mittBus.emit('setSendColumnsChildren', subMenuData)
} else { } else {
console.log('🎯 [Horizontal Menu] onModuleCommand - 无可用子菜单,跳转到模块根路径:', selectedModule.path)
router.push(selectedModule.path) router.push(selectedModule.path)
mittBus.emit('setSendColumnsChildren', { children: [] })
} }
} else { } else {
// console.log('🎯 [Horizontal Menu] onModuleCommand - 模块无子菜单,跳转到模块路径:', selectedModule.path)
router.push(selectedModule.path) router.push(selectedModule.path)
mittBus.emit('setSendColumnsChildren', { children: [] })
}
}
//
const subMenuList = computed(() => {
const module = menuLists.value.find(item => item.path === state.currentModulePath)
return module && module.children
? module.children.filter((child: RouteRecordRaw) => !child.meta?.isHide)
: []
})
//
const currentSubMenuPath = computed(() => {
const sub = subMenuList.value.find((child: RouteRecordRaw) => route.path.startsWith(child.path))
return sub?.path || subMenuList.value[0]?.path || ''
})
//
const setSendSubMenuChildren = (path: string) => {
let res: MittMenu = { children: [] }
const module = menuLists.value.find(item => item.path === state.currentModulePath)
if (module && module.children) {
const sub = module.children.find((child: RouteRecordRaw) => child.path === path)
if (sub) {
res.item = { ...sub }
res.children = sub.children ? [...sub.children] : []
}
} }
// return res
mittBus.emit('getBreadcrumbIndexSetFilterRoutes') }
if (themeConfig.value.layout === 'classic' && themeConfig.value.isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', setSendClassicChildren(path)) const setBeforeRouteUpdateClassicChildren = (path: string) => {
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 开始解析路径:', {
targetPath: path,
currentModulePath: state.currentModulePath
})
let res: MittMenu = { children: [] }
//
const pathSegments = path.split('/').filter(Boolean)
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 路径分割:', {
pathSegments,
level: pathSegments.length
})
//
const module = menuLists.value.find(item => item.path === state.currentModulePath)
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 查找模块:', {
module: module ? {
path: module.path,
title: module.meta?.title,
hasChildren: !!module.children,
childrenCount: module.children?.length || 0
} : null
})
if (module && module.children) {
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 模块子菜单列表:',
module.children.map((child: RouteRecordRaw) => ({
path: child.path,
title: child.meta?.title,
pathStartsWithTarget: path.startsWith(child.path)
}))
)
// children
const findMatchingMenuAndParent = (menuItems: RouteRecordRaw[], targetPath: string, depth: number = 0): { parent: RouteRecordRaw | null, matchedItem: RouteRecordRaw | null } => {
console.log(`🔧 [Horizontal Menu] findMatchingMenuAndParent - 第${depth}层查找:`, {
menuItemsCount: menuItems.length,
targetPath
})
for (const item of menuItems) {
console.log(`🔧 [Horizontal Menu] findMatchingMenuAndParent - 检查菜单项:`, {
itemPath: item.path,
targetPath,
exactMatch: item.path === targetPath,
startsWithMatch: targetPath.startsWith(item.path),
hasChildren: !!item.children
})
//
if (item.path === targetPath) {
console.log(`🔧 [Horizontal Menu] findMatchingMenuAndParent - 找到精确匹配:`, {
matchedPath: item.path,
title: item.meta?.title,
hasChildren: !!item.children,
childrenCount: item.children?.length || 0
})
return { parent: null, matchedItem: item }
}
//
if (targetPath.startsWith(item.path) && item.children && item.children.length > 0) {
console.log(`🔧 [Horizontal Menu] findMatchingMenuAndParent - 路径匹配,递归查找子菜单:`, {
parentPath: item.path,
parentTitle: item.meta?.title
})
const result = findMatchingMenuAndParent(item.children, targetPath, depth + 1)
if (result.matchedItem) {
//
return { parent: item, matchedItem: result.matchedItem }
}
}
}
return { parent: null, matchedItem: null }
}
const { parent, matchedItem } = findMatchingMenuAndParent(module.children, path)
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 匹配结果:', {
matchedItem: matchedItem ? {
path: matchedItem.path,
title: matchedItem.meta?.title,
hasChildren: !!matchedItem.children,
childrenCount: matchedItem.children?.length || 0
} : null,
parent: parent ? {
path: parent.path,
title: parent.meta?.title,
hasChildren: !!parent.children,
childrenCount: parent.children?.length || 0
} : null
})
if (matchedItem) {
// children
// children
if (parent) {
res.item = { ...parent } as RouteItem
res.children = parent.children ? [...parent.children] as RouteItem[] : []
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 使用父级菜单的children:', {
parentPath: parent.path,
parentTitle: parent.meta?.title,
childrenCount: res.children.length
})
} else {
res.item = { ...matchedItem } as RouteItem
res.children = matchedItem.children ? [...matchedItem.children] as RouteItem[] : []
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 使用匹配项的children:', {
itemPath: matchedItem.path,
itemTitle: matchedItem.meta?.title,
childrenCount: res.children.length
})
}
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 构建结果数据:', {
itemPath: res.item?.path,
itemTitle: res.item?.meta?.title,
childrenCount: res.children?.length || 0,
children: res.children?.map(c => ({ path: c.path, title: c.meta?.title })) || []
})
}
} }
console.log('🔧 [Horizontal Menu] setBeforeRouteUpdateClassicChildren - 最终返回数据:', res)
return res
}
//
const onSubMenuClick = (path: string) => {
console.log('📋 [Horizontal Menu] onSubMenuClick - 二级菜单点击:', {
clickedPath: path,
currentRoute: route.path,
currentModule: state.currentModulePath
})
//
console.log('📋 [Horizontal Menu] onSubMenuClick - 执行路由跳转:', path)
router.push(path)
const subMenuData = setSendSubMenuChildren(path)
console.log('📋 [Horizontal Menu] onSubMenuClick - 发送子菜单数据到左侧:', subMenuData)
mittBus.emit('setSendColumnsChildren', subMenuData)
} }
// //
@ -200,142 +407,221 @@ const isCurrentModule = (path: string): boolean => {
} }
// //
onBeforeMount(() => { onBeforeMount(() => {
console.log('🚀 [Horizontal Menu] onBeforeMount - 页面加载前初始化:', {
currentRoute: route.path,
menuListsLength: menuLists.value?.length || 0
})
setCurrentRouterHighlight(route) setCurrentRouterHighlight(route)
console.log('🚀 [Horizontal Menu] onBeforeMount - 设置路由高亮完成')
// //
initCurrentModule() initCurrentModule()
console.log('🚀 [Horizontal Menu] onBeforeMount - 初始化完成:', {
currentModulePath: state.currentModulePath,
defaultActive: state.defaultActive
})
//
const classicChildrenData = setBeforeRouteUpdateClassicChildren(route.path)
console.log('🚀 [Horizontal Menu] onBeforeMount - 发送初始子菜单数据:', {
currentPath: route.path,
data: classicChildrenData
})
mittBus.emit('setSendColumnsChildren', classicChildrenData)
}) })
// //
const initCurrentModule = () => { const initCurrentModule = () => {
console.log('🚀 [Horizontal Menu] initCurrentModule - 初始化当前模块:', {
currentPath: route.path,
availableModules: menuLists.value.map(m => ({ path: m.path, title: m.meta?.title }))
})
// //
const currentPath = route.path const currentPath = route.path
const matchedModule = menuLists.value.find(module => const matchedModule = menuLists.value.find(module =>
currentPath.startsWith(module.path) && module.path !== '/' currentPath.startsWith(module.path) && module.path !== '/'
) )
console.log('🚀 [Horizontal Menu] initCurrentModule - 模块匹配结果:', {
matchedModule: matchedModule ? {
path: matchedModule.path,
title: matchedModule.meta?.title
} : null
})
if (matchedModule) { if (matchedModule) {
state.currentModulePath = matchedModule.path state.currentModulePath = matchedModule.path
console.log('🚀 [Horizontal Menu] initCurrentModule - 设置匹配的模块:', state.currentModulePath)
} else if (menuLists.value.length > 0) { } else if (menuLists.value.length > 0) {
// //
state.currentModulePath = menuLists.value[0].path state.currentModulePath = menuLists.value[0].path
console.log('🚀 [Horizontal Menu] initCurrentModule - 设置默认模块:', state.currentModulePath)
} }
} }
// //
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
console.log('🔄 [Horizontal Menu] onBeforeRouteUpdate - 路由更新开始:', {
from: route.path,
to: to.path,
toMeta: to.meta
})
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G // https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to) setCurrentRouterHighlight(to)
// //
const matchedModule = menuLists.value.find(module => const matchedModule = menuLists.value.find(module =>
to.path.startsWith(module.path) && module.path !== '/' to.path.startsWith(module.path) && module.path !== '/'
) )
console.log('🔄 [Horizontal Menu] onBeforeRouteUpdate - 匹配模块:', {
matchedModule: matchedModule ? {
path: matchedModule.path,
title: matchedModule.meta?.title
} : null,
previousModule: state.currentModulePath
})
if (matchedModule) { if (matchedModule) {
state.currentModulePath = matchedModule.path state.currentModulePath = matchedModule.path
console.log('🔄 [Horizontal Menu] onBeforeRouteUpdate - 更新当前模块路径:', state.currentModulePath)
} }
// tagsView const classicChildrenData = setBeforeRouteUpdateClassicChildren(to.path)
let { layout, isClassicSplitMenu } = themeConfig.value console.log('🔄 [Horizontal Menu] onBeforeRouteUpdate - 发送经典布局子级数据:', {
if (layout === 'classic' && isClassicSplitMenu) { targetPath: to.path,
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path)) data: classicChildrenData
} })
mittBus.emit('setSendClassicChildren', classicChildrenData)
// // tagsView
// let { layout, isClassicSplitMenu } = themeConfig.value
// if (layout === 'classic' && isClassicSplitMenu) {
// mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path))
// }
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.el-menu-horizontal-warp { .el-menu-horizontal-warp {
flex: 1; height: 100%;
overflow: hidden;
margin-right: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; flex: 1;
padding: 0 16px;
background: var(--el-bg-color);
border-bottom: 1px solid var(--el-border-color-light);
.module-dropdown { /* 二级菜单,左侧 */
.module-selector { .sub-menu {
height: 50px; padding: 0 !important;
line-height: 50px; margin: 0 !important;
padding: 0 10px; height: 100%;
cursor: pointer; overflow-x: auto;
white-space: nowrap;
&::-webkit-scrollbar { display: none; }
.el-menu-item {
min-width: 150px;
height: 100%;
line-height: initial;
margin: 0 12px;
padding: 0;
color: var(--next-bg-topBarColor);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 6px;
color: var(--next-bg-topBarColor);
background: transparent;
border: none;
border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: all 0.3s; border-bottom: 2px solid transparent;
white-space: nowrap; transition: color 0.3s, border-color 0.3s;
cursor: pointer;
.module-title { .submenu-icon {
font-weight: 500; margin-top: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 4px;
} }
&:hover { &.is-active {
background: var(--next-color-user-hover); color: var(--el-color-primary);
color: var(--next-bg-topBarColor); border-color: var(--el-color-primary);
font-weight: 600;
} }
&:hover { color: var(--el-color-primary); }
} }
} }
// /* 模块切换下拉,右侧 */
:deep(.el-dropdown-menu) { .module-dropdown {
border-radius: 8px; flex: none;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); margin-left: 16px;
border: 1px solid var(--el-border-color-light); height: 100%;
min-width: 180px; display: flex;
align-items: center;
.el-dropdown-menu__item {
.module-selector {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; height: 100%;
transition: all 0.3s; line-height: initial;
border-radius: 6px; padding: 0 12px;
margin: 4px 6px; gap: 8px;
color: var(--next-bg-topBarColor);
.dropdown-icon { background: var(--el-bg-color);
margin-right: 10px; font-size: 14px;
font-size: 16px; transition: background 0.3s, color 0.3s, border-color 0.3s;
width: 16px; cursor: pointer;
height: 16px;
} .module-title { font-weight: 500; }
&:hover { &:hover {
background: var(--next-color-user-hover); background: var(--el-color-primary-light-9);
color: var(--next-bg-topBarColor); color: var(--el-color-primary);
border-color: var(--el-color-primary-light-7);
} }
&.is-active { /* ensure module icon and dropdown arrow align */
background: var(--next-color-user-hover); .el-icon, i.el-icon {
color: var(--next-bg-topBarColor); font-size: 20px;
font-weight: 600; line-height: 20px;
}
}
:deep(.el-dropdown-menu) {
border-radius: 8px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
border: 1px solid var(--el-border-color-light);
min-width: 180px;
.el-dropdown-menu__item {
display: flex;
align-items: center;
padding: 12px 16px;
margin: 4px 6px;
border-radius: 6px;
transition: background 0.3s, color 0.3s;
cursor: pointer;
.dropdown-icon { .dropdown-icon {
color: var(--next-bg-topBarColor); margin-right: 10px;
font-size: 16px;
width: 16px;
height: 16px;
} }
} &:hover {
} background: var(--el-color-primary-light-9);
} color: var(--el-color-primary);
}
// &.is-active {
:deep(.el-scrollbar__bar.is-vertical) { background: var(--el-color-primary-light-8);
display: none; color: var(--el-color-primary);
} font-weight: 600;
:deep(a) { .dropdown-icon { color: var(--el-color-primary); }
width: 100%;
}
//
@media (max-width: 768px) {
.module-dropdown {
.module-selector {
padding: 0 12px;
height: 40px;
line-height: 40px;
font-size: 14px;
.module-title {
margin-left: 6px;
margin-right: 6px;
} }
} }
} }

View File

@ -13,12 +13,6 @@
<SvgIcon :name="val.meta.icon" /> <SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span> <span>{{ $t(val.meta.title) }}</span>
</template> </template>
<template v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item> </el-menu-item>
</template> </template>
</template> </template>
@ -27,7 +21,6 @@
<script setup lang="ts" name="navMenuSubItem"> <script setup lang="ts" name="navMenuSubItem">
import { computed } from 'vue' import { computed } from 'vue'
import { RouteRecordRaw } from 'vue-router' import { RouteRecordRaw } from 'vue-router'
import other from '/@/utils/other'
// //
const props = defineProps({ const props = defineProps({
@ -40,10 +33,16 @@ const props = defineProps({
// //
const chils = computed(() => { const chils = computed(() => {
console.log('🔗 [Sub Item] 子菜单数据更新:', {
childrenCount: props.chil?.length || 0,
children: props.chil?.map(item => ({
path: item.path,
title: item.meta?.title,
icon: item.meta?.icon,
hasChildren: !!(item.children && item.children.length > 0),
childrenCount: item.children?.length || 0
})) || []
})
return <RouteItems>props.chil return <RouteItems>props.chil
}) })
//
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val)
}
</script> </script>

View File

@ -10,19 +10,24 @@
<template v-for="val in menuLists"> <template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path"> <el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title> <template #title>
<SvgIcon :name="val.meta.icon" /> <SvgIcon :name="val.meta?.icon || ''" />
<span>{{ $t(val.meta.title) }}</span> <span>{{ $t(val.meta?.title || '') }}</span>
</template> </template>
<SubItem :chil="val.children" /> <SubItem :chil="val.children" />
</el-sub-menu> </el-sub-menu>
<template v-else> <template v-else>
<el-menu-item :index="val.path" :key="val.path"> <el-menu-item :index="val.path" :key="val.path">
<SvgIcon :name="val.meta.icon" /> <SvgIcon :name="val.meta?.icon || ''" />
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)"> <template
<span>{{ $t(val.meta.title) }}</span> #title
v-if="!val.meta?.isLink || (val.meta?.isLink && val.meta?.isIframe)"
>
<span>{{ $t(val.meta?.title || '') }}</span>
</template> </template>
<template #title v-else> <template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a> <a class="w100" @click.prevent="onALinkClick(val)">
{{ $t(val.meta?.title || '') }}
</a>
</template> </template>
</el-menu-item> </el-menu-item>
</template> </template>
@ -31,11 +36,13 @@
</template> </template>
<script setup lang="ts" name="navMenuVertical"> <script setup lang="ts" name="navMenuVertical">
import { defineAsyncComponent, reactive, computed, onMounted, watch } from 'vue' import { defineAsyncComponent, reactive, computed, ref, watch, onMounted } from 'vue'
import type { PropType } from 'vue'
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router' import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useThemeConfig } from '/@/stores/themeConfig' import { useThemeConfig } from '/@/stores/themeConfig'
import other from '/@/utils/other' import other from '/@/utils/other'
import mittBus from '/@/utils/mitt'
// //
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')) const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'))
@ -44,7 +51,7 @@ const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue
const props = defineProps({ const props = defineProps({
// //
menuList: { menuList: {
type: Array<RouteRecordRaw>, type: Array as PropType<RouteItem[]>,
default: () => [], default: () => [],
}, },
}) })
@ -58,10 +65,33 @@ const state = reactive({
isCollapse: false, isCollapse: false,
}) })
// //
const menuLists = computed(() => { const localMenuList = ref<RouteItem[]>([])
return <RouteItems>props.menuList
//
localMenuList.value = props.menuList
// menuList
watch(
() => props.menuList,
(newList) => {
localMenuList.value = newList
},
{ immediate: true }
)
//
mittBus.on('setSendColumnsChildren', (res) => {
console.log('📨 [Vertical Menu] 接收到水平菜单数据:', {
event: 'setSendColumnsChildren',
data: res,
childrenLength: res.children?.length || 0
})
localMenuList.value = res.children || []
console.log('📨 [Vertical Menu] 更新本地菜单列表:', localMenuList.value?.length)
}) })
//
const menuLists = computed(() => localMenuList.value)
// //
const getThemeConfig = computed(() => { const getThemeConfig = computed(() => {
return themeConfig.value return themeConfig.value
@ -69,9 +99,27 @@ const getThemeConfig = computed(() => {
// //
const setParentHighlight = (currentRoute: RouteToFrom) => { const setParentHighlight = (currentRoute: RouteToFrom) => {
const { path, meta } = currentRoute const { path, meta } = currentRoute
console.log('🔍 [Vertical Menu] setParentHighlight - 路由信息:', {
path,
meta,
isDynamic: meta?.isDynamic,
isDynamicPath: meta?.isDynamicPath,
isHide: meta?.isHide
})
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/') const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/')
if (pathSplit.length >= 4 && meta?.isHide) return pathSplit.splice(0, 3).join('/') console.log('🔍 [Vertical Menu] setParentHighlight - 路径分割:', pathSplit)
else return path
let highlightPath = ''
if (pathSplit.length >= 4 && meta?.isHide) {
highlightPath = pathSplit.splice(0, 3).join('/')
console.log('🔍 [Vertical Menu] setParentHighlight - 隐藏路由高亮父级:', highlightPath)
} else {
highlightPath = path!
console.log('🔍 [Vertical Menu] setParentHighlight - 常规路由高亮:', highlightPath)
}
return highlightPath
} }
// //
const onALinkClick = (val: RouteItem) => { const onALinkClick = (val: RouteItem) => {
@ -79,12 +127,28 @@ const onALinkClick = (val: RouteItem) => {
} }
// //
onMounted(() => { onMounted(() => {
console.log('🚀 [Vertical Menu] onMounted - 初始化菜单:', {
currentRoute: route.path,
menuLists: menuLists.value?.length
})
state.defaultActive = setParentHighlight(route) state.defaultActive = setParentHighlight(route)
console.log('🚀 [Vertical Menu] onMounted - 设置默认激活菜单:', state.defaultActive)
}) })
// //
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
console.log('🔄 [Vertical Menu] onBeforeRouteUpdate - 路由更新:', {
from: route.path,
to: to.path,
meta: to.meta
})
state.defaultActive = setParentHighlight(to) state.defaultActive = setParentHighlight(to)
console.log('🔄 [Vertical Menu] onBeforeRouteUpdate - 更新默认激活菜单:', state.defaultActive)
const clientWidth = document.body.clientWidth const clientWidth = document.body.clientWidth
console.log('🔄 [Vertical Menu] onBeforeRouteUpdate - 屏幕宽度检查:', {
clientWidth,
needCollapse: clientWidth < 1000
})
if (clientWidth < 1000) themeConfig.value.isCollapse = false if (clientWidth < 1000) themeConfig.value.isCollapse = false
}) })
// / // /

View File

@ -0,0 +1,20 @@
export interface BasicMediumDto {
mediumName: string;
status: boolean;
id: number;
createdTime: string;
modifiedTime: string;
isDeleted: boolean;
}
export interface BasicMediumDtoListServiceResponse {
data?: BasicMediumDto[] | null;
success?: boolean;
message?: string | null;
}
export interface BasicMediumDtoServiceResponse {
data?: BasicMediumDto | null;
success?: boolean;
message?: string | null;
}

1
src/types/mitt.d.ts vendored
View File

@ -65,6 +65,7 @@ declare type MittType<T = any> = {
forceOffline?: T forceOffline?: T
refreshPrintTemplate?: T refreshPrintTemplate?: T
refreshRoom?: T refreshRoom?: T
refreshPump?: T
} }
// mitt 参数类型定义 // mitt 参数类型定义

View File

@ -1,8 +1,14 @@
// https://www.npmjs.com/package/mitt
import mitt, { Emitter } from 'mitt' import mitt, { Emitter } from 'mitt'
// 类型 export interface MittType {
[key: string | symbol]: unknown
refreshMsg: void
refreshFeedMedium: void
refreshBasicMedium: void
refreshCell: void
refreshLiquidBag: void
}
const emitter: Emitter<MittType> = mitt<MittType>() const emitter: Emitter<MittType> = mitt<MittType>()
// 导出
export default emitter export default emitter

View File

@ -0,0 +1,498 @@
<template>
<div class="pump-form">
<el-dialog
v-model="state.isShowDialog"
destroy-on-close
:title="props.title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
<el-form ref="pumpFormRef" :model="state.ruleForm" :rules="state.ruleRules" label-width="120px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo" required>
<el-input v-model="state.ruleForm.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo" required>
<el-input v-model="state.ruleForm.assetNo" placeholder="请输入资产编号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备型号" prop="model" required>
<el-select
v-model="state.ruleForm.model"
placeholder="请选择设备型号"
style="width: 100%"
filterable
allow-create
>
<el-option
v-for="model in state.modelOptions"
:key="model.value"
:label="model.name"
:value="model.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="规格" prop="specification" required>
<el-input v-model="state.ruleForm.specification" placeholder="请输入设备规格" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备负责人" prop="principalId" required>
<el-select
v-model="state.ruleForm.principalId"
placeholder="请选择设备负责人"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="user in state.userOptions"
:key="user.id"
:label="`${user.name} (${user.userName})`"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 技术参数 -->
<div class="form-section">
<div class="section-title">技术参数</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="工作泵速(g/min)" prop="workingSpeed" required>
<el-input-number
v-model="state.ruleForm.workingSpeed"
:min="0"
:precision="2"
style="width: 100%"
placeholder="请输入工作泵速"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="时长:(s)" prop="workingTime" required>
<el-input-number
v-model="state.ruleForm.workingTime"
:min="0"
style="width: 100%"
placeholder="请输入超时时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="泵速系数" prop="speedCoefficient" required>
<el-input-number
v-model="state.ruleForm.speedCoefficient"
:min="0"
:precision="3"
style="width: 100%"
placeholder="请输入泵速系数"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="维护状态" prop="maintenanceFlag">
<el-select
v-model="state.ruleForm.maintenanceFlag"
placeholder="请选择维护状态"
style="width: 100%"
>
<el-option
v-for="status in state.maintenanceStatusOptions"
:key="status.value"
:label="status.label"
:value="status.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 网络配置 -->
<div class="form-section">
<div class="section-title">网络配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="IP地址" prop="ip" required>
<el-input v-model="state.ruleForm.ip" placeholder="请输入IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="端口号" prop="port" required>
<el-input v-model="state.ruleForm.port" placeholder="请输入端口号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="服务名称" prop="serviceName">
<el-input v-model="state.ruleForm.serviceName" placeholder="请输入服务名称" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="state.loading">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="PumpForm">
import { reactive, ref, getCurrentInstance, onMounted } from 'vue'
import { PumpApi } from '/@/api/admin/PumpApi'
import { UserApi } from '/@/api/admin/User'
import { FeedingConfigApi } from '/@/api/admin/FeedingConfigApi'
import { DictApi } from '/@/api/admin/Dict'
import { UserGetPageOutput, PageInputUserGetPageInput, PumpAddInput, PumpUpdateInput, FeedingConfigGetListOutput, DictGetListOutput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
//
const props = defineProps({
title: String,
})
// /
const emit = defineEmits(['refresh'])
//
const pumpFormRef = ref()
const { proxy } = getCurrentInstance() as any
//
type PumpFormData = {
id?: number
deviceNo: string
assetNo: string
model: string
specification: string
pumpSpeed: number
principalId: number | undefined
pumpTimeout: number
maintenanceFlag: number
ip: string
port: string
speedCoefficient: number
serviceName: string
isAirPump: boolean
configId: number | undefined,
workingSpeed: number,
workingTime: number,
}
const state = reactive({
isShowDialog: false,
loading: false,
configLoading: false,
ruleForm: {
id: undefined,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
pumpSpeed: 0,
workingSpeed: 0,
workingTime: 0,
principalId: undefined,
pumpTimeout: 0,
maintenanceFlag: 1,
ip: '',
port: '',
speedCoefficient: 0,
serviceName: '',
isAirPump: false,
configId: undefined,
} as PumpFormData,
userOptions: [] as Array<UserGetPageOutput>,
configOptions: [] as Array<FeedingConfigGetListOutput>,
modelOptions: [] as Array<DictGetListOutput>,
maintenanceStatusOptions: [
{ label: 'Offline', value: 0 },
{ label: 'Idle', value: 1 },
{ label: 'Busy', value: 2 },
{ label: 'Error', value: 3 },
],
ruleRules: {
deviceNo: [
{ required: true, message: '设备编号不能为空', trigger: 'blur' },
{ min: 1, max: 50, message: '设备编号长度应在1-50个字符', trigger: 'blur' }
],
assetNo: [
{ required: true, message: '资产编号不能为空', trigger: 'blur' },
{ min: 1, max: 50, message: '资产编号长度应在1-50个字符', trigger: 'blur' }
],
model: [{ required: true, message: '设备型号不能为空', trigger: 'change' }],
specification: [
{ required: true, message: '规格不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '规格长度应在1-100个字符', trigger: 'blur' }
],
principalId: [{ required: true, message: '设备负责人不能为空', trigger: 'change' }],
pumpSpeed: [{ required: true, message: '泵速不能为空', trigger: 'blur' }],
pumpTimeout: [{ required: true, message: '超时时间不能为空', trigger: 'blur' }],
workingSpeed: [{ required: true, message: '工作泵速不能为空', trigger: 'blur' }],
workingTime: [{ required: true, message: '时长不能为空', trigger: 'blur' }],
speedCoefficient: [{ required: true, message: '泵速系数不能为空', trigger: 'blur' }],
ip: [
{ required: true, message: 'IP地址不能为空', trigger: 'blur' },
{ pattern: /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/, message: '请输入正确的IP地址格式', trigger: 'blur' }
],
port: [
{ required: true, message: '端口号不能为空', trigger: 'blur' },
{ pattern: /^([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/, message: '请输入正确的端口号(1-65535)', trigger: 'blur' }
],
},
})
onMounted(() => {
getUserOptions()
getConfigOptions()
getModelOptions()
})
//
const openDialog = async (row?: any) => {
resetForm()
//
if (state.userOptions.length === 0) {
await getUserOptions()
}
//
if (state.configOptions.length === 0) {
await getConfigOptions()
}
//
if (state.modelOptions.length === 0) {
await getModelOptions()
}
if (row && row.id) {
//
try {
const res = await new PumpApi().get({ id: row.id })
if (res?.success && res.data) {
state.ruleForm = {
id: res.data.id,
deviceNo: res.data.deviceNo || '',
assetNo: res.data.assetNo || '',
model: res.data.model || '',
specification: res.data.specification || '',
pumpSpeed: res.data.pumpSpeed || 0,
principalId: res.data.principalId,
pumpTimeout: res.data.pumpTimeout || 0,
maintenanceFlag: res.data.maintenanceFlag ?? 1,
ip: res.data.ip || '',
port: res.data.port || '',
speedCoefficient: res.data.speedCoefficient || 0,
serviceName: res.data.serviceName || '',
isAirPump: true, // false
configId: res.data.configId,
workingSpeed: res.data.workingSpeed || 0,
workingTime: res.data.workingTime || 0,
}
}
} catch (error: any) {
if (error.response?.status === 500) {
proxy.$modal.msgError('服务器内部错误,请检查后台服务')
} else {
proxy.$modal.msgError(`获取详情失败: ${error.response?.data?.msg || error.message}`)
}
return
}
}
state.isShowDialog = true
}
//
const closeDialog = () => {
state.isShowDialog = false
resetForm()
}
//
const resetForm = () => {
state.ruleForm = {
id: undefined,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
pumpSpeed: 0,
principalId: undefined,
pumpTimeout: 0,
maintenanceFlag: 1,
ip: '',
port: '',
speedCoefficient: 0,
serviceName: '',
isAirPump: false,
configId: undefined,
workingSpeed: 0,
workingTime: 0,
}
}
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000,
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getConfigOptions = async () => {
state.configLoading = true
try {
const res = await new FeedingConfigApi().getList()
if (res?.success && res.data) {
state.configOptions = res.data
}
} catch (error) {
//
} finally {
state.configLoading = false
}
}
//
const getModelOptions = async () => {
try {
const res = await new DictApi().getList(['PUMPTYPE'])
if (res?.success && res.data) {
// APIkey pumptype
state.modelOptions = res.data['pumptype'] || []
}
} catch (error) {
//
}
}
//
const onCancel = () => {
closeDialog()
}
//
const onSubmit = () => {
pumpFormRef.value.validate(async (valid: boolean) => {
if (!valid) return false
state.loading = true
try {
let res = {} as any
// isAirPump false
const formData = { ...state.ruleForm, isAirPump: true }
if (formData.id) {
res = await new PumpApi().update(formData as any, { showSuccessMessage: true })
} else {
res = await new PumpApi().add(formData as any, { showSuccessMessage: true })
}
if (res?.success) {
closeDialog()
emit('refresh')
eventBus.emit('refreshAirPump' as keyof MittType<any>)
}
} catch (error: any) {
proxy.$modal.msgError('操作失败')
} finally {
state.loading = false
}
})
}
//
defineExpose({
openDialog,
closeDialog,
})
</script>
<style scoped lang="scss">
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.queryForm.keyWord" placeholder="设备编号、资产编号" @keyup.enter="Query" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.queryForm.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.queryForm.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button v-if="auth('api:admin:equ-airpump:get-page')" @click="Query" type="primary">
<SvgIcon name="ele-Search" />查询
</el-button>
<el-button v-auth="'api:admin:equ-airpump:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table
v-loading="state.loading"
:data="state.tableData"
stripe
border
style="width: 100%"
>
<el-table-column prop="deviceNo" label="设备编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="model" label="设备型号" min-width="120" show-overflow-tooltip />
<el-table-column prop="specification" label="规格" min-width="120" show-overflow-tooltip />
<el-table-column prop="workingSpeed" label="工作泵速(g/min)" width="110" align="center" />
<el-table-column prop="principalId" label="负责人" width="120" align="center">
<template #default="{ row }">
<span>{{ getPrincipalName(row.principalId) }}</span>
</template>
</el-table-column>
<el-table-column prop="maintenanceFlag" label="维护状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getMaintenanceStatusType(row.maintenanceFlag)">
{{ getMaintenanceStatusText(row.maintenanceFlag) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }">
<el-button
size="small"
type="primary"
@click="onEdit(row)"
v-auth="'api:admin:equ-airpump:update'"
>
编辑
</el-button>
<el-button
size="small"
type="danger"
@click="onDelete(row)"
v-auth="'api:admin:equ-airpump:soft-delete'"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.queryForm.currentPage"
v-model:page-size="state.queryForm.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<pump-form ref="pumpFormRef" :title="state.title" @refresh="Query"></pump-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/airpump">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { ElMessageBox } from 'element-plus'
import { PumpApi } from '/@/api/admin/PumpApi'
import { UserApi } from '/@/api/admin/User'
import { PumpGetPageOutput, PageInputPumpGetPageInput, UserGetPageOutput, PageInputUserGetPageInput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
//
const PumpForm = defineAsyncComponent(() => import('./components/pump-form.vue'))
//
const pumpFormRef = ref()
const { proxy } = getCurrentInstance() as any
const state = reactive({
loading: false,
title: '',
filter: {
keyWord: '',
stDate: '',
edDate: '',
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputPumpGetPageInput,
queryForm: {
keyWord: '',
stDate: '',
edDate: '',
currentPage: 1,
pageSize: 20,
},
tableData: [] as Array<PumpGetPageOutput>,
userOptions: [] as Array<UserGetPageOutput>,
showQuery: true,
showPumpList: true,
})
onMounted(() => {
getUserOptions()
Query()
eventBus.off('refreshAirPump' as keyof MittType<any>)
eventBus.on('refreshAirPump' as keyof MittType<any>, () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshAirPump' as keyof MittType<any>)
})
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000,
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getPrincipalName = (principalId?: number) => {
if (!principalId) return '-'
const user = state.userOptions.find(u => u.id === principalId)
return user ? `${user.name} (${user.userName})` : '-'
}
//
const getMaintenanceStatusText = (status?: number) => {
switch (status) {
case 0:
return 'Offline'
case 1:
return 'Idle'
case 2:
return 'Busy'
case 3:
return 'Error'
default:
return '未知'
}
}
//
const getMaintenanceStatusType = (status?: number) => {
switch (status) {
case 0:
return 'info'
case 1:
return 'success'
case 2:
return 'warning'
case 3:
return 'danger'
default:
return ''
}
}
//
const Query = async () => {
state.loading = true
try {
const queryParams = {
currentPage: state.queryForm.currentPage,
pageSize: state.queryForm.pageSize,
filter: {
keyWord: state.queryForm.keyWord || null,
stDate: state.queryForm.stDate || null,
edDate: state.queryForm.edDate || null,
isAirPump: true, // false
}
} as PageInputPumpGetPageInput
const res = await new PumpApi().getPage(queryParams)
if (res?.success) {
state.tableData = res.data?.list ?? []
state.total = res.data?.total ?? 0
}
} catch (error: any) {
proxy.$modal.msgError(`查询失败: ${error.response?.data?.msg || error.message}`)
} finally {
state.loading = false
}
}
//
const onReset = () => {
state.queryForm = {
keyWord: '',
stDate: '',
edDate: '',
currentPage: 1,
pageSize: 20,
}
Query()
}
//
const onSizeChange = (size: number) => {
state.queryForm.pageSize = size
Query()
}
//
const onCurrentChange = (page: number) => {
state.queryForm.currentPage = page
Query()
}
//
const onAdd = () => {
state.title = '新增空气泵'
pumpFormRef.value.openDialog()
}
//
const onEdit = (row: PumpGetPageOutput) => {
state.title = '编辑空气泵'
pumpFormRef.value.openDialog(row)
}
//
const onDelete = (row: PumpGetPageOutput) => {
ElMessageBox.confirm(`确定要删除空气泵【${row.deviceNo}】吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
const res = await new PumpApi().softDelete({ id: row.id! })
if (res?.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error: any) {
proxy.$modal.msgError(`删除失败: ${error.response?.data?.msg || error.message}`)
}
})
.catch(() => {})
}
</script>

View File

@ -0,0 +1,189 @@
<template>
<div>
<el-dialog v-model="state.showDialog" destroy-on-close :title="title" draggable :close-on-click-modal="false"
:close-on-press-escape="false" width="900px">
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo"
:rules="[{ required: true, message: '请输入设备编号', trigger: ['blur', 'change'] }]">
<el-input v-model="form.deviceNo" clearable placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo">
<el-input v-model="form.assetNo" clearable placeholder="请输入资产编号" />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="form-section">
<div class="section-title">网络配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="IP地址" prop="ip" :rules="[{ required: true, message: '请输入IP地址', trigger: 'blur' }]">
<el-input v-model="form.ip" clearable placeholder="请输入IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="端口" prop="port" :rules="[{ required: true, message: '请输入端口', trigger: 'blur' }]">
<el-input v-model="form.port" clearable placeholder="请输入端口" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="服务名称" prop="serviceName">
<el-input v-model="form.serviceName" clearable placeholder="请输入服务名称" />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="form-section">
<div class="section-title">设备配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="启用状态">
<el-switch
v-model="form.status"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSure" size="default" :loading="state.sureLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="platform/nppusp/device/alarm-form">
import { reactive, toRefs, ref, onMounted } from 'vue';
import { AlarmUpdateInput, RoomGetPageOutput, PageInputRoomGetPageInput, AlarmAddInput } from '/@/api/admin/data-contracts';
import { AlarmApi } from '/@/api/admin/AlarmApi';
import { ElMessage } from 'element-plus';
const props = defineProps({
title: {
type: String,
default: '',
},
});
const emits = defineEmits(['refresh']);
const formRef = ref();
const state = reactive({
showDialog: false,
sureLoading: false,
form: {
status: true,
isRuning: 0,
} as AlarmUpdateInput,
roomOptions: [] as Array<RoomGetPageOutput>,
});
const { form } = toRefs(state);
onMounted(() => {
});
const open = async (row: any = { id: 0 }) => {
if (row.id > 0) {
const res = await new AlarmApi().get({ id: row.id });
if (res?.success) {
state.form = res.data as AlarmUpdateInput;
}
} else {
state.form = {
id: 0,
deviceNo: '',
assetNo: '',
roomId: undefined,
ip: '',
port: '',
serviceName: '',
status: true,
isRuning: 0,
};
}
state.showDialog = true;
};
const onCancel = () => {
state.showDialog = false;
};
const onSure = () => {
formRef.value.validate(async (valid: boolean) => {
if (!valid) return;
state.sureLoading = true;
let res;
state.form.isRuning = state.form.isRuning ?? 0;
if (state.form.id > 0) {
res = await new AlarmApi().update(state.form as AlarmUpdateInput);
} else {
res = await new AlarmApi().add(state.form as AlarmAddInput);
}
state.sureLoading = false;
if (res?.success) {
ElMessage.success('操作成功');
emits('refresh');
onCancel();
} else {
ElMessage.error(res?.msg as string);
}
});
};
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="设备编号、资产编号" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:usp-alarm:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增报警器 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table :data="state.alarmListData" style="width: 100%" v-loading="state.loading" row-key="id" border>
<el-table-column prop="deviceNo" label="设备编号" min-width="150" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="150" show-overflow-tooltip />
<el-table-column prop="ip" label="IP地址" min-width="130" show-overflow-tooltip />
<el-table-column prop="port" label="端口" min-width="80" align="center" />
<el-table-column prop="serviceName" label="服务名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="isRunning" label="运行状态" width="100" align="center">
<template #default="{ row }">
<el-tag type="primary" v-if="row.isRuning">运行中</el-tag>
<el-tag type="info" v-else>停止</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:usp-alarm:update'" icon="ele-EditPen" size="small" text type="primary" @click="onEdit(row)">编辑</el-button>
<el-button v-auth="'api:admin:usp-alarm:soft-delete'" icon="ele-Delete" size="small" text type="danger" @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<alarm-form ref="alarmFormRef" :title="state.alarmFormTitle" @refresh="onQuery" />
</my-layout>
</template>
<script lang="ts" setup name="/admin/alarm">
import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent } from 'vue';
import { PageInputAlarmGetPageInput, AlarmGetPageOutput } from '/@/api/admin/data-contracts';
import { AlarmApi } from '/@/api/admin/AlarmApi';
import { ElMessageBox, ElMessage } from 'element-plus';
import { auth } from '/@/utils/authFunction'
const AlarmForm = defineAsyncComponent(() => import('./components/alarm-form.vue'));
const { proxy } = getCurrentInstance() as any;
const alarmFormRef = ref();
const state = reactive({
loading: false,
alarmFormTitle: '',
filter: {
keyWord: '',
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
filter: {},
} as PageInputAlarmGetPageInput,
alarmListData: [] as Array<AlarmGetPageOutput>,
});
onMounted(() => {
onQuery();
});
const onQuery = async () => {
state.loading = true;
state.pageInput.filter = state.filter;
const res = await new AlarmApi().getPage(state.pageInput).catch(() => {
state.loading = false;
});
if (res?.success) {
state.alarmListData = res.data?.list ?? [];
state.total = res.data?.total ?? 0;
}
state.loading = false;
};
const onAdd = () => {
state.alarmFormTitle = '新增报警器';
alarmFormRef.value.open();
};
const onEdit = (row: AlarmGetPageOutput) => {
state.alarmFormTitle = '编辑报警器';
alarmFormRef.value.open(row);
};
const onDelete = (row: AlarmGetPageOutput) => {
ElMessageBox.confirm(`确定要删除报警器【${row.deviceNo}】吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
const res = await new AlarmApi().softDelete({ id: row.id });
if (res?.success) {
ElMessage.success('删除成功');
onQuery();
} else {
ElMessage.error(res?.msg as string);
}
})
.catch(() => {});
};
const onSizeChange = (val: number) => {
state.pageInput.pageSize = val;
onQuery();
};
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val;
onQuery();
};
</script>

View File

@ -0,0 +1,181 @@
<template>
<div>
<el-dialog
v-model="dialogVisible"
destroy-on-close
:title="formData.id ? '编辑培养基' : '新增培养基'"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
size="default"
>
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="培养基名称" prop="mediumName" :rules="[{ required: true, message: '请输入培养基名称', trigger: ['blur', 'change'] }]">
<el-input v-model="form.mediumName" clearable placeholder="请输入培养基名称" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="启用状态">
<el-switch
v-model="form.status"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel" size="default"> </el-button>
<el-button type="primary" @click="handleSubmit" size="default" :loading="loading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { ItemDefBasicMediumApi } from '/@/api/admin/item-def-basic-medium'
import type { BasicMediumDto } from '/@/types/data-contracts'
const props = defineProps<{
visible: boolean
formData: Partial<BasicMediumDto>
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}>()
const api = new ItemDefBasicMediumApi()
const formRef = ref<FormInstance>()
const loading = ref(false)
// 使 visible
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
})
const form = reactive<Partial<BasicMediumDto>>({
mediumName: '',
status: true
})
const rules = {
mediumName: [
{ required: true, message: '请输入培养基名称', trigger: ['blur', 'change'] }
]
}
// formData
watch(
() => props.formData,
(newVal) => {
if (newVal) {
Object.assign(form, newVal)
}
},
{ immediate: true }
)
// visible
watch(
() => props.visible,
(newVal) => {
if (!newVal) {
formRef.value?.resetFields()
}
}
)
const handleCancel = () => {
emit('update:visible', false)
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
//
const submitData: BasicMediumDto = {
mediumName: form.mediumName || '',
status: form.status ?? true,
id: form.id || 0,
createdTime: form.createdTime || new Date().toISOString(),
modifiedTime: new Date().toISOString(),
isDeleted: false
}
if (form.id) {
//
const res = await api.update(submitData)
if (res.success) {
ElMessage.success('修改成功')
emit('success')
emit('update:visible', false)
} else {
ElMessage.error('修改失败')
}
} else {
//
const res = await api.add(submitData)
if (res.success) {
ElMessage.success('新增成功')
emit('success')
emit('update:visible', false)
} else {
ElMessage.error(res.msg || '新增失败')
}
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
} finally {
loading.value = false
}
}
})
}
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
padding-left: 8px;
border-left: 4px solid var(--el-color-primary);
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>

View File

@ -0,0 +1,252 @@
<template>
<MyLayout>
<el-card v-show="showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="searchForm.keyWord" placeholder="培养基名称" @keyup.enter="loadData" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="searchForm.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="searchForm.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="loadData">查询</el-button>
<el-button type="primary" icon="ele-Plus" @click="handleAdd" v-permission="'api:admin:item-def-basic-medium:add'">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-loading="loading"
:data="tableData"
style="width: 100%"
border
>
<el-table-column prop="mediumName" label="培养基名称" min-width="300" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="200" align="center">
<template #default="{ row }">
<el-tag :type="row.status ? 'success' : 'danger'">
{{ row.status ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDateTime(row.createdTime) }}
</template>
</el-table-column>
<el-table-column prop="modifiedTime" label="修改时间" width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDateTime(row.modifiedTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button type="primary" icon="ele-EditPen" size="small" text @click="handleEdit(row)" v-permission="'api:admin:item-def-basic-medium:get'">编辑</el-button>
<el-button
type="danger"
icon="ele-Delete"
size="small"
text
@click="handleDelete(row)"
v-permission="'api:admin:item-def-basic-medium:soft-delete'"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<basic-liquid-form
v-model:visible="formVisible"
:form-data="formData"
@success="handleSuccess"
/>
</MyLayout>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ItemDefBasicMediumApi } from '/@/api/admin/item-def-basic-medium'
import type { BasicMediumDto } from '/@/types/data-contracts'
import MyLayout from '/@/components/my-layout/index.vue'
import BasicLiquidForm from './components/BasicLiquidForm.vue'
const api = new ItemDefBasicMediumApi()
const loading = ref(false)
const tableData = ref<BasicMediumDto[]>([])
const formVisible = ref(false)
const formData = ref<Partial<BasicMediumDto>>({})
const showQuery = ref(true)
const searchForm = reactive({
keyWord: '',
stDate: '',
edDate: ''
})
const pagination = reactive({
total: 0,
current: 1,
pageSize: 10
})
//
const formatDateTime = (dateTime: string | undefined) => {
if (!dateTime) return ''
const date = new Date(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
//
const loadData = async () => {
loading.value = true
try {
const res = await api.getPage({
currentPage: pagination.current,
pageSize: pagination.pageSize,
filter: searchForm
})
if (res.success) {
tableData.value = res.data?.list || []
pagination.total = res.data?.total || 0
} else {
ElMessage.error(res.msg || '加载数据失败')
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
}
}
const handleAdd = () => {
formData.value = {}
formVisible.value = true
}
const handleEdit = (row: BasicMediumDto) => {
formData.value = { ...row }
formVisible.value = true
}
const handleDelete = async (row: BasicMediumDto) => {
ElMessageBox.confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await api.softDelete({ id: row.id })
if (res.success) {
ElMessage.success('删除成功')
loadData()
} else {
ElMessage.error(res.msg || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}).catch(() => {})
}
const handleSuccess = () => {
formVisible.value = false
loadData()
}
const handleSizeChange = (val: number) => {
pagination.pageSize = val
loadData()
}
const handleCurrentChange = (val: number) => {
pagination.current = val
loadData()
}
onMounted(() => {
loadData()
})
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-tools-box {
margin-bottom: 16px;
}
.my-flex {
display: flex;
}
.my-flex-between {
justify-content: space-between;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="500px"
:close-on-click-modal="false"
:close-on-press-escape="false"
append-to-body
destroy-on-close
draggable
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
@submit.prevent
>
<el-form-item label="细胞编号" prop="cellNo">
<el-input v-model="form.cellNo" placeholder="请输入细胞编号" />
</el-form-item>
<el-form-item label="细胞名称" prop="cellName">
<el-input v-model="form.cellName" placeholder="请输入细胞名称" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="onSubmit"> </el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue'
import { CellDto } from '/@/api/admin/data-contracts'
import { ItemDefCellApi } from '/@/api/admin/item-def-cell'
import eventBus from '/@/utils/mitt'
const props = defineProps({
title: {
type: String,
default: ''
}
})
const dialogVisible = ref(false)
const formRef = ref()
const form = reactive<Partial<CellDto>>({
cellNo: '',
cellName: '',
status: true
})
const rules = {
cellNo: [{ required: true, message: '请输入细胞编号', trigger: 'blur' }],
cellName: [{ required: true, message: '请输入细胞名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const open = (row: Partial<CellDto>) => {
dialogVisible.value = true
Object.assign(form, row)
}
const onSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
const submitData = {
...form,
id: form.id || 0
}
if (submitData.id) {
await new ItemDefCellApi().update(submitData as CellDto, { loading: true })
} else {
await new ItemDefCellApi().add(submitData as CellDto, { loading: true })
}
dialogVisible.value = false
eventBus.emit('refreshCell')
}
})
}
defineExpose({
open
})
</script>
<style scoped lang="scss">
:deep(.el-form-item__label) {
text-align: right;
padding-right: 12px;
}
</style>

View File

@ -0,0 +1,221 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="细胞编号、细胞名称" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:item-def-cell:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-if="state.showCellList"
:data="state.cellListData"
style="width: 100%"
v-loading="state.loading"
border
>
<el-table-column prop="cellNo" label="细胞编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="cellName" label="细胞名称" min-width="120" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.status">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDate(new Date(row.createdTime), 'YYYY-mm-dd HH:MM:SS') }}
</template>
</el-table-column>
<el-table-column prop="modifiedTime" label="修改时间" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
{{ formatDate(new Date(row.modifiedTime), 'YYYY-mm-dd HH:MM:SS') }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:item-def-cell:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:item-def-cell:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:current-page="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<cell-form ref="cellFormRef" :title="state.cellFormTitle"></cell-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/cell">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { CellDto, PageInputCellDtoGetPageInput } from '/@/api/admin/data-contracts'
import { ItemDefCellApi } from '/@/api/admin/item-def-cell'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
import { formatDate } from '/@/utils/formatTime'
//
const CellForm = defineAsyncComponent(() => import('./components/CellForm.vue'))
const { proxy } = getCurrentInstance() as any
const cellFormRef = ref()
const state = reactive({
loading: false,
cellFormTitle: '',
filter: {
keyWord: '',
stDate: '',
edDate: ''
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
filter: {
keyWord: '',
stDate: '',
edDate: ''
}
} as PageInputCellDtoGetPageInput,
cellListData: [] as Array<CellDto>,
showQuery: true,
showCellList: true,
})
onMounted(() => {
Query()
eventBus.off('refreshCell')
eventBus.on('refreshCell', () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshCell')
})
const onChangeCellList = () => {
state.showCellList = !state.showCellList
if (state.showCellList) {
Query()
}
}
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
const res = await new ItemDefCellApi().getPage(state.pageInput).catch(() => {
state.loading = false
})
state.cellListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
state.loading = false
}
const onAdd = () => {
state.cellFormTitle = '新增细胞类型'
cellFormRef.value.open({
id: 0,
cellNo: '',
cellName: '',
status: true,
createdTime: '',
modifiedTime: '',
isDeleted: false
})
}
const onEdit = (row: CellDto) => {
state.cellFormTitle = '编辑细胞类型'
cellFormRef.value.open(row)
}
const onDelete = (row: CellDto) => {
proxy.$modal
.confirmDelete(`确定要删除细胞类型【${row.cellName}】?`)
.then(async () => {
await new ItemDefCellApi().softDelete({ id: row.id }, { loading: true })
Query()
})
.catch(() => {})
}
const onSizeChange = (val: number) => {
state.pageInput.currentPage = 1
state.pageInput.pageSize = val
onQuery()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
onQuery()
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,200 @@
<template>
<el-dialog
v-model="visible"
:title="title"
width="600px"
:before-close="handleClose"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
@keyup.enter="onSubmit"
>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="批次名称" prop="preBatchName">
<el-input
v-model="form.preBatchName"
placeholder="请输入批次名称"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="批次编号" prop="preBatchNo">
<el-input
v-model="form.preBatchNo"
placeholder="请输入批次编号"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="项目" prop="projectId">
<el-select
v-model="form.projectId"
placeholder="请选择项目"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="loading">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { PreBatchApi } from '/@/api/admin/PreBatchApi'
import { ProjectApi } from '/@/api/admin/ProjectApi'
import type { PreBatchDto } from '/@/api/types/preBatchType'
interface Props {
title?: string
}
const props = withDefaults(defineProps<Props>(), {
title: '批次信息'
})
const emit = defineEmits<{
ok: []
}>()
const visible = ref(false)
const loading = ref(false)
const formRef = ref<FormInstance>()
const isEdit = ref(false)
const form = reactive<PreBatchDto>({
id: undefined,
preBatchName: '',
preBatchNo: '',
projectId: null,
isDown: true,
titer: null
})
const projectOptions = ref<Array<{ id: number; name: string }>>([])
const rules: FormRules = {
preBatchName: [
{ required: true, message: '请输入批次名称', trigger: 'blur' },
{ max: 255, message: '批次名称长度不能超过255个字符', trigger: 'blur' }
],
preBatchNo: [
{ required: true, message: '请输入批次编号', trigger: 'blur' },
{ max: 50, message: '批次编号长度不能超过50个字符', trigger: 'blur' }
],
projectId: [
{ required: true, message: '请选择项目', trigger: 'change' }
]
}
onMounted(() => {
getProjectOptions()
})
const open = (data?: PreBatchDto) => {
visible.value = true
isEdit.value = !!data?.id
nextTick(() => {
if (data) {
Object.assign(form, data)
} else {
resetForm()
}
formRef.value?.clearValidate()
})
}
const resetForm = () => {
Object.assign(form, {
id: undefined,
preBatchName: '',
preBatchNo: '',
projectId: null,
isDown: true,
titer: null
})
}
const handleClose = () => {
visible.value = false
loading.value = false
}
const onSubmit = async () => {
try {
await formRef.value?.validate()
loading.value = true
const api = new PreBatchApi()
const res = isEdit.value
? await api.update(form)
: await api.add(form)
if (res.success) {
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
emit('ok')
handleClose()
}
} catch (error) {
console.error('提交失败:', error)
} finally {
loading.value = false
}
}
const getProjectOptions = async () => {
try {
const res = await new ProjectApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
projectOptions.value = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.projectName ?? ''
})) ?? []
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
defineExpose({
open
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
</style>

View File

@ -0,0 +1,344 @@
<template>
<MyLayout>
<el-card class="my-query-box" shadow="never">
<el-form :model="state.filter" :inline="true" @keyup.enter="onQuery">
<el-form-item label="关键字">
<el-input
v-model="state.filter.keyWord"
placeholder="批次名称/编号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="项目">
<el-select
v-model="state.filter.projectId"
placeholder="请选择项目"
clearable
style="width: 200px"
>
<el-option
v-for="item in state.projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="date"
placeholder="开始时间"
style="width: 150px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="date"
placeholder="结束时间"
style="width: 150px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery">查询</el-button>
<el-button v-auth="'api:admin:pre-batch:add'" type="primary" icon="ele-Plus" @click="onAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table
:data="state.preBatchListData"
style="width: 100%"
v-loading="state.loading"
row-key="id"
border
>
<el-table-column prop="preBatchName" label="批次名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="preBatchNo" label="批次编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="projectId" label="项目" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getProjectName(row.projectId) }}
</template>
</el-table-column>
<el-table-column prop="projectUserName" label="项目负责人" min-width="100" show-overflow-tooltip />
<el-table-column prop="createdUserRealName" label="创建者" min-width="100" show-overflow-tooltip />
<el-table-column prop="createdTime" label="创建时间" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<div class="action-buttons">
<el-button
v-if="auth('api:admin:pre-batch:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:pre-batch:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</div>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<PrebatchForm ref="preBatchFormRef" :title="state.formTitle" @ok="Query" />
</MyLayout>
</template>
<script lang="ts" setup name="admin/dspBatch">
import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent } from 'vue'
import { PreBatchApi } from '/@/api/admin/PreBatchApi'
import { ProjectApi } from '/@/api/admin/ProjectApi'
import type { PreBatchDto, PreBatchPageInput, PreBatchPageDto } from '/@/api/types/preBatchType'
import { auth } from '/@/utils/authFunction'
//
const PrebatchForm = defineAsyncComponent(() => import('./components/prebatch-form.vue'))
const { proxy } = getCurrentInstance() as any
const preBatchFormRef = ref()
const state = reactive({
loading: false,
formTitle: '',
filter: {
keyWord: '',
projectId: null as number | null,
isDown: true,
stDate: '',
edDate: ''
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PreBatchPageInput,
preBatchListData: [] as Array<PreBatchPageDto>,
projectOptions: [] as Array<{ id: number; name: string }>,
})
onMounted(() => {
Query()
getProjectOptions()
})
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
try {
const res = await new PreBatchApi().getPage(state.pageInput)
state.preBatchListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
} catch (error) {
console.error('查询失败:', error)
} finally {
state.loading = false
}
}
const onAdd = () => {
state.formTitle = '新增预批次'
preBatchFormRef.value?.open()
}
const onEdit = (row: PreBatchDto) => {
state.formTitle = '编辑预批次'
preBatchFormRef.value?.open(row)
}
const onDelete = (row: PreBatchDto) => {
proxy.$modal.confirm('确认要删除该记录吗?').then(async () => {
try {
const res = await new PreBatchApi().softDelete({ id: row.id! })
if (res.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
const onSizeChange = (val: number) => {
state.pageInput.pageSize = val
Query()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
Query()
}
const onReset = () => {
state.filter = {
keyWord: '',
projectId: null,
isDown: true,
stDate: '',
edDate: ''
}
Query()
}
const getProjectOptions = async () => {
try {
const res = await new ProjectApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.projectOptions = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.projectName ?? ''
})) ?? []
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
const getProjectName = (projectId: number) => {
const project = state.projectOptions.find(item => item.id === projectId)
return project?.name ?? ''
}
</script>
<style lang="scss" scoped>
.my-query-box {
margin-top: 8px;
:deep(.el-form-item) {
margin-bottom: 16px;
}
:deep(.el-form--inline .el-form-item) {
margin-right: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
:deep(.el-card__body) {
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
}
.my-flex {
display: flex;
align-items: center;
}
.my-flex-between {
justify-content: space-between;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
//
:deep(.el-table) {
.el-table__header-wrapper {
th {
background-color: #fafafa;
color: #606266;
font-weight: 500;
}
}
.el-table__body-wrapper {
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
}
//
:deep(.el-pagination) {
display: flex;
justify-content: flex-end;
margin-top: 16px;
.el-pager li.is-active {
background-color: var(--el-color-primary);
color: white;
}
}
//
.action-buttons {
display: flex;
gap: 8px;
.el-button {
margin-left: 0;
}
}
//
.status-tag {
&.enabled {
background-color: #f0f9ff;
color: #1890ff;
border: 1px solid #d4edda;
}
&.disabled {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
}
</style>

View File

@ -0,0 +1,562 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="1200px"
class="custom-dialog"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
append-to-body
destroy-on-close
:style="{ height: '95vh' }"
@close="close"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="220px"
size="default"
v-loading="loading"
>
<el-row :gutter="25">
<!-- 阶段 -->
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card shadow="hover" class="group-card">
<div class="section-title">阶段</div>
<el-form-item label="策略名称" prop="configName" required>
<el-input v-model="formData.configName" clearable placeholder="请输入策略名称" />
</el-form-item>
<el-form-item label="两段式" prop="isTwoStep">
<el-switch v-model="formData.isTwoStep" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="总理论时长" prop="totalTheoryTime" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.totalTheoryTime" disabled style="flex: 1;" />
<span style="margin-left: 8px; white-space: nowrap;">分钟</span>
</div>
</el-form-item>
<div class="sub-group" v-if="formData.isTwoStep !== 1">
<div class="sub-title">校准</div>
<el-form-item v-if="formData.isTwoStep !== 1" label="校准阶段时长" prop="calibrateDuration" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.calibrateDuration" :min="120" :max="300" style="flex: 1;" placeholder="请输入120-300之间的秒数" />
<span style="margin-left: 8px; white-space: nowrap;"></span>
</div>
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="校准阶段补料占总量百分比" prop="calibrateVolumePercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.calibrateVolumePercent" :min="6" :max="20" style="flex: 1;" placeholder="请输入6-20之间的百分比" />
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="校准理论速度" prop="calibrateTheorySpeed" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.calibrateTheorySpeed" :min="0" style="flex: 1;" disabled />
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
</div>
<div class="sub-group">
<div class="sub-title">高速</div>
<el-form-item label="高速阶段泵速百分比" prop="highSpeedPercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number
v-model="formData.highSpeedPercent"
:min="formData.isTwoStep === 1 ? 1 : 2"
:max="formData.isTwoStep === 1 ? 100 : 50"
style="flex: 1;"
:placeholder="formData.isTwoStep === 1 ? '请输入1-100之间的百分比' : '请输入2-50之间的百分比'"
/>
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item label="高速阶段补料占总量百分比" prop="highSpeedVolumePercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number
v-model="formData.highSpeedVolumePercent"
:min="formData.isTwoStep === 1 ? 50 : 60"
:max="formData.isTwoStep === 1 ? 90 : 80"
style="flex: 1;"
:placeholder="formData.isTwoStep === 1 ? '请输入50-90之间的百分比' : '请输入60-80之间的百分比'"
/>
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="高速理论时长" prop="highSpeedTheoryTime" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.highSpeedTheoryTime" :min="0" style="flex: 1;" disabled />
<span style="margin-left: 8px; white-space: nowrap;">分钟</span>
</div>
</el-form-item>
</div>
<div class="sub-group">
<div class="sub-title">低速</div>
<el-form-item label="低速阶段泵速百分比" prop="lowSpeedPercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number
v-model="formData.lowSpeedPercent"
:min="formData.isTwoStep === 1 ? 1 : 0.5"
:max="formData.isTwoStep === 1 ? 100 : 20"
style="flex: 1;"
:placeholder="formData.isTwoStep === 1 ? '请输入1-100之间的百分比' : '请输入0.5-20之间的百分比'"
/>
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="低速阶段补料占总量百分比" prop="lowSpeedVolumePercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.lowSpeedVolumePercent" :min="2" :max="50" style="flex: 1;" placeholder="请输入2-50之间的百分比" />
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item v-if="formData.isTwoStep !== 1" label="低速理论时长" prop="lowSpeedTheoryTime" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.lowSpeedTheoryTime" :min="0" style="flex: 1;" disabled />
<span style="margin-left: 8px; white-space: nowrap;">分钟</span>
</div>
</el-form-item>
</div>
</el-card>
</el-col>
<!-- 监控 -->
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<el-card shadow="hover" class="group-card">
<div class="section-title">监控</div>
<el-form-item prop="intervalTime" required>
<template #label>
环控间隔时间
<el-tooltip content="监控每次启动间隔时间" placement="top">
<el-icon style="margin-left: 4px; margin-top: 6px; color: #909399; cursor: pointer;">
<QuestionFilled />
</el-icon>
</el-tooltip>
</template>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.intervalTime" :min="1" style="flex: 1;" placeholder="请输入大于0的秒数" />
<span style="margin-left: 8px; white-space: nowrap;"></span>
</div>
</el-form-item>
<el-form-item label="补料超量百分比" prop="excessPercentage" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.excessPercentage" :min="1" style="flex: 1;" placeholder="请输入大于0的百分比" />
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<el-form-item label="超时时间" prop="timeOutSeconds" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.timeOutSeconds" :min="1" style="flex: 1;" placeholder="请输入大于0的秒数" />
<span style="margin-left: 8px; white-space: nowrap;"></span>
</div>
</el-form-item>
<el-form-item label="终点百分比" prop="terminalVolumePercent" required>
<div style="display: flex; align-items: center; width: 100%;">
<el-input-number v-model="formData.terminalVolumePercent" :min="98" :max="100" style="flex: 1;" placeholder="请输入98-100之间的百分比" />
<span style="margin-left: 8px; white-space: nowrap;">%</span>
</div>
</el-form-item>
<div class="sub-group">
<div class="sub-title">抖动</div>
<el-form-item label="抖动环控时间系数" prop="ditherIntervalCoefficient" required>
<el-input-number v-model="formData.ditherIntervalCoefficient" :min="1" :max="5" style="width:100%" placeholder="请输入1-5之间的数值" />
</el-form-item>
<el-form-item label="抖动系数" prop="ditherCoefficient" required>
<el-input-number v-model="formData.ditherCoefficient" :min="2" :max="10" style="width:100%" placeholder="请输入2-10之间的数值" />
</el-form-item>
<el-form-item label="抖动观察时间系数" prop="ditherMonitorTime" required>
<el-input-number v-model="formData.ditherMonitorTime" :min="1" style="width:100%" placeholder="请输入大于0的数值" />
</el-form-item>
<el-form-item label="抖动地秤系数" prop="ditherScaleCoefficient" required>
<el-input-number v-model="formData.ditherScaleCoefficient" :min="1" style="width:100%" placeholder="请输入大于0的数值" />
</el-form-item>
</div>
<div class="sub-group">
<div class="sub-title">返水</div>
<el-form-item label="返水环控时间系数" prop="rebateIntervalCoefficient" required>
<el-input-number v-model="formData.rebateIntervalCoefficient" disabled style="width:100%" placeholder="自动等于抖动环控时间系数" />
</el-form-item>
<el-form-item label="返水系数" prop="rebateCoefficient" required>
<el-input-number v-model="formData.rebateCoefficient" :max="-1" style="width:100%" placeholder="请输入小于等于-1的数值" />
</el-form-item>
</div>
<div class="sub-group">
<div class="sub-title">堵塞</div>
<el-form-item label="堵塞环控时间系数" prop="jamIntervalCoefficient" required>
<el-input-number v-model="formData.jamIntervalCoefficient" :min="1" style="width:100%" placeholder="请输入大于0的数值" />
</el-form-item>
<el-form-item label="堵塞系数" prop="jamCoefficient" required>
<el-input-number v-model="formData.jamCoefficient" :min="0" :max="1" style="width:100%" placeholder="请输入0-1之间的数值" />
</el-form-item>
</div>
</el-card>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="close" size="default"> </el-button>
<el-button type="primary" @click="handleSubmit" size="default" :loading="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { UspFeedingConfigApi } from '/@/api/admin/UspFeedingConfigApi'
import { QuestionFilled } from '@element-plus/icons-vue'
const props = defineProps<{
modelValue: boolean
title: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'refresh'): void
}>()
const formRef = ref<FormInstance>()
const formData = reactive({
configName: '',
totalTheoryTime: undefined as number | undefined,
calibrateDuration: undefined as number | undefined,
calibrateVolumePercent: undefined as number | undefined,
calibrateTheorySpeed: undefined as number | undefined,
highSpeedPercent: undefined as number | undefined,
highSpeedVolumePercent: undefined as number | undefined,
highSpeedTheoryTime: undefined as number | undefined,
lowSpeedPercent: undefined as number | undefined,
lowSpeedVolumePercent: undefined as number | undefined,
lowSpeedTheoryTime: undefined as number | undefined,
intervalTime: undefined as number | undefined,
excessPercentage: undefined as number | undefined,
timeOutSeconds: undefined as number | undefined,
terminalVolumePercent: undefined as number | undefined,
ditherIntervalCoefficient: undefined as number | undefined,
ditherCoefficient: undefined as number | undefined,
ditherMonitorTime: undefined as number | undefined,
ditherScaleCoefficient: undefined as number | undefined,
rebateIntervalCoefficient: undefined as number | undefined,
rebateCoefficient: undefined as number | undefined,
jamIntervalCoefficient: undefined as number | undefined,
jamCoefficient: undefined as number | undefined,
enabled: true,
isTwoStep: 0,
id: undefined as number | undefined
})
const rules = {
configName: [{ required: true, message: '请输入策略名称', trigger: ['blur', 'change'] }],
totalTheoryTime: [{ required: true, message: '请输入总理论时长', trigger: ['blur', 'change'] }],
calibrateDuration: [{ required: true, message: '请输入校准阶段时长', trigger: ['blur', 'change'] }],
calibrateVolumePercent: [{ required: true, message: '请输入校准阶段补料占总量百分比', trigger: ['blur', 'change'] }],
calibrateTheorySpeed: [{ required: true, message: '请输入校准理论速度', trigger: ['blur', 'change'] }],
highSpeedPercent: [{ required: true, message: '请输入高速阶段泵速百分比', trigger: ['blur', 'change'] }],
highSpeedVolumePercent: [{ required: true, message: '请输入高速阶段补料占总量百分比', trigger: ['blur', 'change'] }],
highSpeedTheoryTime: [{ required: true, message: '请输入高速理论时长', trigger: ['blur', 'change'] }],
lowSpeedPercent: [{ required: true, message: '请输入低速阶段泵速百分比', trigger: ['blur', 'change'] }],
lowSpeedVolumePercent: [{ required: true, message: '请输入低速阶段补料占总量百分比', trigger: ['blur', 'change'] }],
lowSpeedTheoryTime: [{ required: true, message: '请输入低速理论时长', trigger: ['blur', 'change'] }],
intervalTime: [{ required: true, message: '请输入环控间隔时间', trigger: ['blur', 'change'] }],
excessPercentage: [{ required: true, message: '请输入补料超量百分比', trigger: ['blur', 'change'] }],
timeOutSeconds: [{ required: true, message: '请输入超时时间', trigger: ['blur', 'change'] }],
terminalVolumePercent: [{ required: true, message: '请输入终点百分比', trigger: ['blur', 'change'] }],
ditherIntervalCoefficient: [{ required: true, message: '请输入抖动环控时间系数', trigger: ['blur', 'change'] }],
ditherCoefficient: [{ required: true, message: '请输入抖动系数', trigger: ['blur', 'change'] }],
ditherMonitorTime: [{ required: true, message: '请输入抖动观察时间系数', trigger: ['blur', 'change'] }],
ditherScaleCoefficient: [{ required: true, message: '请输入抖动地秤系数', trigger: ['blur', 'change'] }],
rebateIntervalCoefficient: [{ required: true, message: '请输入返水环控时间系数', trigger: ['blur', 'change'] }],
rebateCoefficient: [{ required: true, message: '请输入返水系数', trigger: ['blur', 'change'] }],
jamIntervalCoefficient: [{ required: true, message: '请输入堵塞环控时间系数', trigger: ['blur', 'change'] }],
jamCoefficient: [{ required: true, message: '请输入堵塞系数', trigger: ['blur', 'change'] }]
}
const loading = ref(false)
const dialogVisible = ref(false)
const api = new UspFeedingConfigApi()
watch(
() => props.modelValue,
(val) => {
dialogVisible.value = val
}
)
watch(
() => dialogVisible.value,
(val) => {
emit('update:modelValue', val)
if (!val) resetForm()
}
)
watch(
() => [formData.calibrateVolumePercent, formData.calibrateDuration, formData.isTwoStep],
([volume, duration]) => {
if (formData.isTwoStep === 1) return;
if (typeof volume === 'number' && typeof duration === 'number' && duration > 0) {
formData.calibrateTheorySpeed = Number((volume / duration * 60).toFixed(2)) as number
} else {
formData.calibrateTheorySpeed = undefined
}
}
)
//
watch(
() => [formData.highSpeedVolumePercent, formData.highSpeedPercent, formData.isTwoStep],
([volume, percent]) => {
if (formData.isTwoStep === 1) return;
if (typeof volume === 'number' && typeof percent === 'number' && percent > 0) {
formData.highSpeedTheoryTime = Number((volume / percent).toFixed(2)) as number
} else {
formData.highSpeedTheoryTime = undefined
}
}
)
//
watch(
() => [formData.lowSpeedVolumePercent, formData.lowSpeedPercent, formData.isTwoStep],
([volume, percent]) => {
if (formData.isTwoStep === 1) return;
if (typeof volume === 'number' && typeof percent === 'number' && percent > 0) {
formData.lowSpeedTheoryTime = Number((volume / percent).toFixed(2)) as number
} else {
formData.lowSpeedTheoryTime = undefined
}
}
)
//
watch(
() => [formData.calibrateDuration, formData.highSpeedTheoryTime, formData.lowSpeedTheoryTime, formData.isTwoStep],
([calibrateDuration, highSpeedTheoryTime, lowSpeedTheoryTime]) => {
if (formData.isTwoStep === 1) return;
if (typeof calibrateDuration === 'number' && typeof highSpeedTheoryTime === 'number' && typeof lowSpeedTheoryTime === 'number') {
formData.totalTheoryTime = Number(((calibrateDuration / 60) + highSpeedTheoryTime + lowSpeedTheoryTime).toFixed(2)) as number
} else {
formData.totalTheoryTime = undefined
}
}
)
watch(
() => [formData.isTwoStep, formData.highSpeedPercent],
([isTwoStep, highSpeedPercent]) => {
if (isTwoStep === 1) {
formData.lowSpeedVolumePercent = 100 - Number(highSpeedPercent || 0)
}
}
)
// =
watch(
() => formData.ditherIntervalCoefficient,
(val) => {
formData.rebateIntervalCoefficient = val
}
)
function resetForm() {
formRef.value?.resetFields()
Object.assign(formData, {
configName: '',
totalTheoryTime: undefined,
calibrateDuration: undefined,
calibrateVolumePercent: undefined,
calibrateTheorySpeed: undefined,
highSpeedPercent: undefined,
highSpeedVolumePercent: undefined,
highSpeedTheoryTime: undefined,
lowSpeedPercent: undefined,
lowSpeedVolumePercent: undefined,
lowSpeedTheoryTime: undefined,
intervalTime: undefined,
excessPercentage: undefined,
timeOutSeconds: undefined,
terminalVolumePercent: undefined,
ditherIntervalCoefficient: undefined,
ditherCoefficient: undefined,
ditherMonitorTime: undefined,
ditherScaleCoefficient: undefined,
rebateIntervalCoefficient: undefined,
rebateCoefficient: undefined,
jamIntervalCoefficient: undefined,
jamCoefficient: undefined,
enabled: true,
isTwoStep: 0,
id: undefined
})
}
function setFormData(data: any) {
Object.assign(formData, data)
}
function close() {
dialogVisible.value = false
}
async function open(row?: any) {
dialogVisible.value = true
if (row && row.id) {
loading.value = true
try {
const res = await api.get({ id: row.id })
if (res.success && res.data) setFormData(res.data)
else ElMessage.warning(res.msg || '获取数据失败')
} catch (error) {
console.error(error)
ElMessage.error('获取数据出错')
} finally {
loading.value = false
}
} else resetForm()
}
async function handleSubmit() {
if (!formRef.value) return
// isTwoStep !== 1
if (formData.isTwoStep !== 1) {
const sum = Number(formData.calibrateVolumePercent) + Number(formData.highSpeedVolumePercent) + Number(formData.lowSpeedVolumePercent)
if (sum !== 100) {
ElMessage.warning('补料占总量百分比之和必须等于100')
return
}
}
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
// undefinednull
const data: Record<string, any> = { ...formData }
Object.keys(data).forEach((key: string) => {
if (typeof data[key] === 'undefined') data[key] = null
})
if (data.id == null) delete data.id
if (formData.id) {
const updateInput = {
...data,
setSpeed: data.calibrateTheorySpeed,
speedDiff: 0,
feedTime: data.totalTheoryTime,
id: data.id,
configName: data.configName,
intervalTime: data.intervalTime,
calibrateDuration: data.calibrateDuration,
calibrateVolumePercent: data.calibrateVolumePercent,
highSpeedPercent: data.highSpeedPercent,
highSpeedVolumePercent: data.highSpeedVolumePercent,
lowSpeedPercent: data.lowSpeedPercent,
lowSpeedVolumePercent: data.lowSpeedVolumePercent,
ditherIntervalCoefficient: data.ditherIntervalCoefficient,
ditherCoefficient: data.ditherCoefficient,
ditherMonitorTime: data.ditherMonitorTime,
ditherScaleCoefficient: data.ditherScaleCoefficient,
rebateIntervalCoefficient: data.rebateIntervalCoefficient,
rebateCoefficient: data.rebateCoefficient,
jamIntervalCoefficient: data.jamIntervalCoefficient,
jamCoefficient: data.jamCoefficient,
excessPercentage: data.excessPercentage,
timeOutSeconds: data.timeOutSeconds,
terminalVolumePercent: data.terminalVolumePercent,
enabled: data.enabled,
isTwoStep: data.isTwoStep
}
await api.update(updateInput)
ElMessage.success('修改成功')
} else {
const addInput = {
...data,
setSpeed: data.calibrateTheorySpeed,
speedDiff: 0,
feedTime: data.totalTheoryTime
}
delete (addInput as any).id
await api.add(addInput)
ElMessage.success('新增成功')
}
emit('refresh')
close()
} catch (error) {
console.error(error)
ElMessage.error('提交失败')
} finally {
loading.value = false
}
})
}
defineExpose({ open })
</script>
<style scoped>
.group-card {
margin-bottom: 15px;
border-radius: 10px;
box-shadow: 0 2px 12px #f0f1f2;
background: #fff;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: #409EFF;
margin-bottom: 18px;
letter-spacing: 2px;
}
.sub-group {
background: #f7fafd;
border-radius: 8px;
padding: 12px 16px 4px 16px;
margin-bottom: 18px;
margin-top: 18px;
}
.sub-title {
font-size: 15px;
color: #6ca0dc;
font-weight: bold;
margin-bottom: 10px;
letter-spacing: 1px;
}
.el-form-item {
margin-bottom: 18px !important;
}
@media (max-width: 1200px) {
.group-card {
margin-bottom: 16px;
}
}
.ant-card::before, .ant-card::after,
.el-card::before, .el-card::after {
border: none !important;
box-shadow: none !important;
}
/* 针对所有 input、select、textarea */
form input,
form select,
form textarea {
border: none !important;
box-shadow: none !important;
outline: none !important;
}
/* 针对常见UI库的表单项容器 */
.ant-input, .el-input__inner, .n-input__input {
border: none !important;
box-shadow: none !important;
outline: none !important;
}
/* 针对分组容器 */
.ant-card, .el-card, .card, .fieldset, .form-group {
border: none !important;
box-shadow: none !important;
}
</style>

View File

@ -0,0 +1,259 @@
<template>
<MyLayout>
<el-card class="my-query-box mt8" shadow="never">
<template #header>
<el-form ref="queryFormRef" :model="state.filter" :inline="true" class="demo-form-inline">
<el-form-item label="策略名称">
<el-input v-model="state.filter.keyWord" placeholder="请输入策略名称" clearable />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery">查询</el-button>
<el-button v-if="hasAuth('api:admin:usp-feeding-config:add')" type="primary" icon="ele-Plus" @click="handleAdd">
新增
</el-button>
</el-form-item>
</el-form>
</template>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div></div>
</div>
<el-table
v-loading="state.loading"
:data="state.tableData"
style="width: 100%"
border
>
<el-table-column prop="configName" label="策略名称" min-width="120" show-overflow-tooltip />
<el-table-column label="补料方式" min-width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="info" v-if="row.isTwoStep === 1">二段式</el-tag>
<el-tag type="success " v-else>三段式</el-tag>
</template>
</el-table-column>
<el-table-column prop="feedTime" label="补料时长 min" min-width="100" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.isTwoStep === 1">-</span>
<span v-else>{{ row.feedTime }}</span>
</template>
</el-table-column>
<el-table-column prop="calibrateDuration" label="校准阶段时长/s" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.isTwoStep === 1">-</span>
<span v-else>{{ row.calibrateDuration }}</span>
</template>
</el-table-column>
<el-table-column prop="calibrateVolumePercent" label="校准阶段补料占总量百分比/%" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.isTwoStep === 1">-</span>
<span v-else>{{ row.calibrateVolumePercent }}</span>
</template>
</el-table-column>
<el-table-column prop="highSpeedPercent" label="高速阶段泵速比例/%" min-width="140" show-overflow-tooltip />
<el-table-column prop="highSpeedVolumePercent" label="高速阶段补料占比/%" min-width="140" show-overflow-tooltip />
<el-table-column prop="lowSpeedPercent" label="低速阶段泵速比例/%" min-width="140" show-overflow-tooltip />
<el-table-column prop="lowSpeedVolumePercent" label="低速阶段补料占比/%" min-width="140" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.enabled">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleEdit(row)"
v-if="hasAuth('api:admin:usp-feeding-config:update')"
>
编辑
</el-button>
<el-button
type="danger"
link
@click="handleDelete(row)"
v-if="hasAuth('api:admin:usp-feeding-config:soft-delete')"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:current-page="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="state.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<feeding-config-form
ref="formRef"
v-model="showForm"
@refresh="getList"
title="泵速策略"
/>
</MyLayout>
</template>
<script lang="ts" setup name="admin/feeding-config">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UspFeedingConfigApi } from '/@/api/admin/UspFeedingConfigApi'
import {
PageInputFeedingConfigGetPageInput,
FeedingConfigGetPageOutput,
FeedingConfigGetPageInput
} from '/@/api/admin/data-contracts'
import FeedingConfigForm from './components/feeding-config-form.vue'
import { auth as hasAuth } from '/@/utils/authFunction'
import { filter } from 'lodash-es'
const { proxy } = getCurrentInstance() as any
const formRef = ref()
const state = reactive({
loading: false,
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputFeedingConfigGetPageInput,
tableData: [] as FeedingConfigGetPageOutput[],
filter: {
keyWord: '',
stDate: '',
edDate: ''
},
})
// API
const uspFeedingConfigApi = new UspFeedingConfigApi() // HttpClient
const getList = async () => {
state.loading = true
try {
state.pageInput.filter = {
keyWord: state.filter.keyWord || '',
stDate: state.filter.stDate || null,
edDate: state.filter.edDate || null,
}
const res = await uspFeedingConfigApi.getPage(state.pageInput)
console.log(res)
if (res.success && res.data) {
state.tableData = res.data.list || []
state.total = res.data.total || 0
} else {
state.tableData = []
state.total = 0
ElMessage.warning(res.msg || '获取数据失败')
}
} catch (error) {
console.error('获取列表数据出错:', error)
ElMessage.error('获取列表数据出错')
} finally {
state.loading = false
}
}
const handleQuery = () => {
state.pageInput.currentPage = 1
getList()
}
const resetQuery = () => {
state.filter.keyWord = ''
state.filter.stDate = ''
state.filter.edDate = ''
handleQuery()
}
const handleSizeChange = (val: number) => {
state.pageInput.pageSize = val
getList()
}
const handleCurrentChange = (val: number) => {
state.pageInput.currentPage = val
getList()
}
const handleAdd = () => {
formRef.value?.open()
}
const handleEdit = (row: FeedingConfigGetPageOutput) => {
formRef.value?.open(row)
}
const handleDelete = (row: FeedingConfigGetPageOutput) => {
ElMessageBox.confirm('确认要删除该泵速策略吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
console.log(row)
const res = await uspFeedingConfigApi.softDelete({ id: row.id })
if (res) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除出错:', error)
ElMessage.error('删除失败')
}
}).catch(() => {})
}
const showForm = ref(false)
onMounted(() => {
getList()
})
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="500px"
:close-on-click-modal="false"
:close-on-press-escape="false"
append-to-body
destroy-on-close
draggable
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="180px"
@submit.prevent
>
<el-form-item label="培养基编号" prop="mediumCode">
<el-input v-model="form.mediumCode" placeholder="请输入培养基编号" />
</el-form-item>
<el-form-item label="培养基名称" prop="mediumName">
<el-input v-model="form.mediumName" placeholder="请输入培养基名称" />
</el-form-item>
<el-form-item label="补料培养基含糖浓度(g/L)" prop="glucoseConc">
<el-input-number v-model="form.glucoseConc" :min="0" :precision="2" :step="0.1" />
</el-form-item>
<el-form-item label="是否含葡萄糖" prop="isGlucose">
<el-switch v-model="form.isGlucose" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { FeedMediumDto } from '/@/api/admin/data-contracts'
import { ItemDefFeedingMediumApi } from '/@/api/admin/item-def-feeding-medium'
import eventBus from '/@/utils/mitt'
const props = defineProps<{
title: string
}>()
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const form = reactive<Partial<FeedMediumDto>>({
mediumCode: '',
mediumName: '',
glucoseConc: 0,
isGlucose: true,
status: true
})
const rules = {
mediumCode: [{ required: true, message: '请输入培养基编号', trigger: 'blur' }],
mediumName: [{ required: true, message: '请输入培养基名称', trigger: 'blur' }],
glucoseConc: [{ required: true, message: '请输入葡萄糖浓度', trigger: 'blur' }],
isGlucose: [{ required: true, message: '请选择是否含葡萄糖', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const open = (data?: Partial<FeedMediumDto>) => {
dialogVisible.value = true
if (data) {
Object.assign(form, data)
} else {
Object.assign(form, {
mediumCode: '',
mediumName: '',
glucoseConc: 0,
isGlucose: true,
status: true
})
}
}
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (form.id) {
await new ItemDefFeedingMediumApi().update(form as FeedMediumDto)
ElMessage.success('修改成功')
} else {
await new ItemDefFeedingMediumApi().add(form as FeedMediumDto)
ElMessage.success('添加成功')
}
dialogVisible.value = false
eventBus.emit('refreshFeedMedium')
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败')
}
}
})
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.el-input-number {
width: 180px;
}
</style>

View File

@ -0,0 +1,252 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="培养基编号、培养基名称" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:item-def-feeding-medium:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-if="state.showFeedMediumList"
:data="state.feedMediumListData"
style="width: 100%"
v-loading="state.loading"
border
>
<el-table-column prop="mediumCode" label="培养基编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="mediumName" label="培养基名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="glucoseConc" label="补料培养基含糖浓度(g/L)" min-width="120" show-overflow-tooltip />
<el-table-column label="是否含葡萄糖" width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.isGlucose"></el-tag>
<el-tag type="danger" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.status">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" min-width="200" show-overflow-tooltip />
<el-table-column prop="modifiedTime" label="修改时间" min-width="200" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:item-def-feeding-medium:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:item-def-feeding-medium:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<feeding-medium-form ref="feedingMediumFormRef" :title="state.feedingMediumFormTitle"></feeding-medium-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/feedliquid">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { FeedMediumDto, PageInputFeedMediumDtoGetPageInput, FeedMediumDtoGetPageOutput } from '/@/api/admin/data-contracts'
import { ItemDefFeedingMediumApi } from '/@/api/admin/item-def-feeding-medium'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
//
const FeedingMediumForm = defineAsyncComponent(() => import('./components/FeedingMediumForm.vue'))
const { proxy } = getCurrentInstance() as any
const feedingMediumFormRef = ref()
const state = reactive({
loading: false,
feedingMediumFormTitle: '',
filter: {
keyWord: '',
stDate: '',
edDate: ''
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputFeedMediumDtoGetPageInput,
feedMediumListData: [] as Array<FeedMediumDto>,
showQuery: true,
showFeedMediumList: true,
})
onMounted(() => {
Query()
eventBus.off('refreshFeedMedium')
eventBus.on('refreshFeedMedium', () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshFeedMedium')
})
const onChangeFeedMediumList = () => {
state.showFeedMediumList = !state.showFeedMediumList
if (state.showFeedMediumList) {
Query()
}
}
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
const res = await new ItemDefFeedingMediumApi().getPage(state.pageInput).catch(() => {
state.loading = false
})
state.feedMediumListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
state.loading = false
}
const onAdd = () => {
state.feedingMediumFormTitle = '新增补料培养基'
feedingMediumFormRef.value.open({
id: 0,
mediumCode: '',
mediumName: '',
glucoseConc: 0,
isGlucose: true,
status: true,
createdTime: '',
modifiedTime: '',
isDeleted: false
})
}
const onEdit = (row: FeedMediumDto) => {
state.feedingMediumFormTitle = '编辑补料培养基'
feedingMediumFormRef.value.open(row)
}
const onDelete = (row: FeedMediumDto) => {
proxy.$modal
.confirmDelete(`确定要删除补料培养基【${row.mediumName}】?`)
.then(async () => {
await new ItemDefFeedingMediumApi().softDelete({ id: row.id }, { loading: true })
Query()
})
.catch(() => {})
}
const onSizeChange = (val: number) => {
state.pageInput.currentPage = 1
state.pageInput.pageSize = val
onQuery()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
onQuery()
}
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-tools-box {
margin-bottom: 16px;
}
.my-flex {
display: flex;
}
.my-flex-between {
justify-content: space-between;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,236 @@
<template>
<el-dialog v-model="state.dialogVisible" :title="props.title" width="900px" :close-on-click-modal="false"
:close-on-press-escape="false" draggable destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" @submit.prevent>
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="描述" prop="itemDescription">
<el-input v-model="form.itemDescription" placeholder="请输入描述" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="型号" prop="model">
<el-select v-model="form.model" placeholder="请选择型号" style="width: 100%;">
<el-option v-for="item in state.modelList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="容量(g)" prop="capacity">
<el-input-number v-model="form.capacity" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="皮重(g)" prop="tareWeight">
<el-input-number v-model="form.tareWeight" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="下限体积(g)" prop="lowerLimitVolume">
<el-input-number v-model="form.lowerLimitVolume" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="残余量" prop="residualVolume">
<el-input-number v-model="form.residualVolume" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="图标" prop="materialIcon">
<el-upload class="h100 personal-user-left-upload" :action="iconAction" :headers="iconHeaders"
:data="{ autoUpdate: true }" :show-file-list="false" :before-upload="() => {
state.token = storesUserInfo.getToken()
state.iconLoading = true
}
" :on-success="iconSuccess" :on-error="iconError">
<img :src="iconUrl" />
</el-upload>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="state.dialogVisible = false" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.sureLoading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, onMounted, getCurrentInstance } from 'vue'
import { BagDto, BagModelEnum } from '/@/api/admin/data-contracts'
import { ItemDefBagApi } from '/@/api/admin/item-def-bag'
import { DictApi } from '/@/api/admin/Dict'
import eventBus from '/@/utils/mitt'
import { useUserInfo } from '/@/stores/userInfo'
import pinia from '/@/stores/index'
import { storeToRefs } from 'pinia'
import { AxiosResponse } from 'axios'
const props = defineProps({
title: {
type: String,
default: ''
}
})
const { proxy } = getCurrentInstance() as any
const formRef = ref()
const storesUserInfo = useUserInfo(pinia)
const { userInfos } = storeToRefs(storesUserInfo)
const state = reactive({
dialogVisible: false,
sureLoading: false,
modelList: [] as BagModelEnum[],
iconLoading: false,
token: storesUserInfo.getToken(),
})
const form = reactive<Partial<BagDto>>({
id: 0,
itemDescription: '',
model: undefined,
capacity: 0,
tareWeight: 0,
lowerLimitVolume: 0,
residualVolume: 0,
status: true,
materialIcon: '',
})
const rules = {
itemDescription: [{ required: true, message: '请输入描述', trigger: 'blur' }],
model: [{ required: true, message: '请选择型号', trigger: 'change' }],
capacity: [{ required: true, message: '请输入容量', trigger: 'blur' }],
tareWeight: [{ required: true, message: '请输入皮重', trigger: 'blur' }],
lowerLimitVolume: [{ required: true, message: '请输入下限体积', trigger: 'blur' }],
residualVolume: [{ required: true, message: '请输入残余量', trigger: 'blur' }],
}
onMounted(async () => {
try {
const res = await new DictApi().getList(['bag_model'])
if (res?.data?.bag_model) {
state.modelList = res.data.bag_model.map((item: any) => ({
value: Number(item.value) || 1,
label: item.name || '',
}))
}
} catch (error) {
console.error('获取型号列表失败:', error)
}
})
const open = (row?: Partial<BagDto>) => {
state.dialogVisible = true
if (row) {
Object.assign(form, row)
if (typeof row.model === 'object' && row.model !== null) {
form.model = row.model
}
} else {
Object.assign(form, {
id: 0,
itemDescription: '',
model: undefined,
capacity: 0,
tareWeight: 0,
lowerLimitVolume: 0,
residualVolume: 0,
status: true,
})
}
}
const onSubmit = async () => {
if (!formRef.value) return
state.sureLoading = true
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
if (form.id) {
await new ItemDefBagApi().update(form as BagDto, { loading: true })
} else {
await new ItemDefBagApi().add(form as BagDto, { loading: true })
}
state.dialogVisible = false
eventBus.emit('refreshLiquidBag')
}
})
state.sureLoading = false
}
const iconUrl = computed(() => {
return form.materialIcon || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'
})
const iconAction = computed(() => {
return import.meta.env.VITE_API_URL + '/api/admin/user/avatar-upload'
})
const iconHeaders = computed(() => {
return { Authorization: 'Bearer ' + state.token }
})
const iconSuccess = (res: AxiosResponse) => {
state.iconLoading = false
if (!res?.success) {
if (res.msg) {
proxy.$modal.msgError(res.msg)
}
return
}
form.materialIcon = res.data
}
const iconError = (error: any) => {
state.iconLoading = false
console.error('上传失败:', error)
}
defineExpose({
open
})
</script>
<style scoped lang="scss">
.form-section {
border: 1px solid #dcdfe6;
padding: 20px;
margin-bottom: 20px;
border-radius: 4px;
position: relative;
}
.section-title {
position: absolute;
top: -10px;
left: 10px;
background-color: #fff;
padding: 0 10px;
font-size: 16px;
font-weight: bold;
}
:deep(.el-upload) {
height: 90px;
width: 90px;
img {
width: 90px;
height: 90px;
}
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="描述" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:item-def-bag:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table
:data="state.bagListData"
style="width: 100%"
v-loading="state.loading"
border
>
<el-table-column prop="itemDescription" label="描述" min-width="180" show-overflow-tooltip />
<el-table-column label="型号" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getModelName(row.model) }}
</template>
</el-table-column>
<el-table-column prop="capacity" label="容量(g)" min-width="120" show-overflow-tooltip />
<el-table-column prop="tareWeight" label="皮重(g)" min-width="120" show-overflow-tooltip />
<el-table-column prop="lowerLimitVolume" label="下限体积(g)" min-width="120" show-overflow-tooltip />
<el-table-column prop="residualVolume" label="残余量(g)" min-width="120" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.status">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" :formatter="formatterTime" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:item-def-bag:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:item-def-bag:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<liquid-bag-form ref="liquidBagFormRef" :title="state.formTitle"></liquid-bag-form>
</my-layout>
</template>
<script lang="ts" setup name="admin/liquidbag">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { BagDto, PageInputBagDtoGetPageInput } from '/@/api/admin/data-contracts'
import { ItemDefBagApi } from '/@/api/admin/item-def-bag'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
import dayjs from 'dayjs'
import { BagModelEnum } from '/@/api/admin/data-contracts'
import { DictApi } from '/@/api/admin/Dict'
const LiquidBagForm = defineAsyncComponent(() => import('./components/liquid-bag-form.vue'))
const { proxy } = getCurrentInstance() as any
const liquidBagFormRef = ref()
const state = reactive({
loading: false,
formTitle: '',
filter: {
keyWord: '',
stDate: '',
edDate: ''
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
filter: {
keyWord: '',
stDate: '',
edDate: ''
},
} as PageInputBagDtoGetPageInput,
bagListData: [] as Array<BagDto>,
modelOptions: [] as BagModelEnum[],
})
onMounted(async () => {
getModelOptions()
onQuery()
eventBus.on('refreshLiquidBag', () => {
onQuery()
})
})
onBeforeMount(() => {
eventBus.off('refreshLiquidBag')
})
const formatterTime = (row: any, column: any, cellValue: any) => {
return cellValue ? dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss') : ''
}
const getModelOptions = async () => {
const res = await new DictApi().getList(['bag_model'])
if (res?.data?.bag_model) {
state.modelOptions = res.data.bag_model.map((item: any) => ({
value: Number(item.value) || 1,
label: item.name || '',
}))
}
}
const onQuery = async () => {
state.loading = true
if (state.pageInput.filter) {
state.pageInput.filter.keyWord = state.filter.keyWord
state.pageInput.filter.stDate = state.filter.stDate || null
state.pageInput.filter.edDate = state.filter.edDate
}
const res = await new ItemDefBagApi().getPage(state.pageInput).catch(() => {
state.loading = false
})
state.bagListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
state.loading = false
}
const onAdd = () => {
state.formTitle = '新增液袋'
liquidBagFormRef.value.open()
}
const onEdit = (row: BagDto) => {
state.formTitle = '编辑液袋'
liquidBagFormRef.value.open(row)
}
const onDelete = (row: BagDto) => {
proxy.$modal
.confirmDelete(`确定要删除项目【${row.itemDescription}】?`)
.then(async () => {
await new ItemDefBagApi().softDelete({ id: row.id }, { loading: true })
onQuery()
})
.catch(() => {})
}
const onSizeChange = (val: number) => {
state.pageInput.currentPage = 1
state.pageInput.pageSize = val
onQuery()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
onQuery()
}
const getModelName = (modelValue: number) => {
const model = state.modelOptions.find(item => item.value === modelValue)
return model ? model.label : modelValue
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,342 @@
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { EquPrinterApi } from '/@/api/admin/EquPrinterApi'
import type { EquPrinterAddInput, EquPrinterUpdateInput } from '/@/api/admin/data-contracts'
const props = defineProps<{
modelValue: boolean
title: string
formData: Partial<EquPrinterAddInput> & { id?: number }
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'success'): void
}>()
// ref
const formRef = ref<FormInstance>()
//
const form = reactive<EquPrinterAddInput & { id?: number }>({
deviceNo: '',
assetNo: '',
ip: '',
port: 9100,
printerIP: '',
printerPort: '',
printerHeight: '',
printerHeightOne: '',
printerHeightTwo: '',
printerHeightThree: '',
printerWidth: '',
printerWidthOne: '',
printerWidthTwo: '',
printerWidthThree: '',
printerFont: '',
printerFontOne: '',
printerFontTwo: '',
printerFontThree: '',
printerNumber: 1,
isEnabled: true
})
//
const rules = {
deviceNo: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
assetNo: [{ required: true, message: '请输入资产编号', trigger: 'blur' }],
ip: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
port: [{ required: true, message: '请输入端口号', trigger: 'blur' }],
printerIP: [{ required: true, message: '请输入打印机IP', trigger: 'blur' }],
printerPort: [{ required: true, message: '请输入打印机端口', trigger: 'blur' }],
printerHeight: [{ required: true, message: '请输入打印高度', trigger: 'blur' }],
printerWidth: [{ required: true, message: '请输入打印宽度', trigger: 'blur' }],
printerFont: [{ required: true, message: '请输入打印字体', trigger: 'blur' }],
printerNumber: [{ required: true, message: '请输入打印数量', trigger: 'blur' }]
}
//
const dialogVisible = ref(false)
// modelValue
watch(() => props.modelValue, (val) => {
dialogVisible.value = val
})
//
watch(() => dialogVisible.value, (val) => {
emit('update:modelValue', val)
if (!val) {
resetForm()
}
})
//
watch(() => props.formData, (val) => {
if (val) {
Object.assign(form, val)
}
}, { deep: true, immediate: true })
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields()
}
Object.assign(form, {
deviceNo: '',
assetNo: '',
ip: '',
port: 9100,
printerIP: '',
printerPort: '',
printerHeight: '',
printerHeightOne: '',
printerHeightTwo: '',
printerHeightThree: '',
printerWidth: '',
printerWidthOne: '',
printerWidthTwo: '',
printerWidthThree: '',
printerFont: '',
printerFontOne: '',
printerFontTwo: '',
printerFontThree: '',
printerNumber: 1,
isEnabled: true
})
}
//
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
try {
const api = new EquPrinterApi()
if (form.id) {
const res = await api.update(form)
if (res.success) {
ElMessage.success('修改成功')
dialogVisible.value = false
emit('success')
}
} else {
const res = await api.add(form)
if (res.success) {
ElMessage.success('添加成功')
dialogVisible.value = false
emit('success')
}
}
} catch (error) {
console.error('提交表单失败:', error)
}
}
})
}
</script>
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="900px"
append-to-body
destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="false"
draggable
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
size="default"
class="printer-form"
>
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="form.deviceNo" clearable placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo">
<el-input v-model="form.assetNo" clearable placeholder="请输入资产编号" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 网络配置 -->
<div class="form-section">
<div class="section-title">网络配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="IP地址" prop="ip">
<el-input v-model="form.ip" clearable placeholder="请输入IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="端口号" prop="port">
<el-input-number v-model="form.port" :min="1" :max="65535" style="width: 100%" placeholder="请输入端口号" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 参数配置 -->
<div class="form-section">
<div class="section-title">参数配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第一行宽度" prop="printerWidth">
<el-input v-model="form.printerWidth" clearable placeholder="请输入第一行宽度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第一行高度" prop="printerHeight">
<el-input v-model="form.printerHeight" clearable placeholder="请输入第一行高度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第一行字体" prop="printerFont">
<el-input v-model="form.printerFont" clearable placeholder="请输入第一行字体" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第二行宽度" prop="printerWidthOne">
<el-input v-model="form.printerWidthOne" clearable placeholder="请输入第二行宽度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第二行高度" prop="printerHeightOne">
<el-input v-model="form.printerHeightOne" clearable placeholder="请输入第二行高度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第二行字体" prop="printerFontOne">
<el-input v-model="form.printerFontOne" clearable placeholder="请输入第二行字体" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第三行宽度" prop="printerWidthTwo">
<el-input v-model="form.printerWidthTwo" clearable placeholder="请输入第三行宽度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第三行高度" prop="printerHeightTwo">
<el-input v-model="form.printerHeightTwo" clearable placeholder="请输入第三行高度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第三行字体" prop="printerFontTwo">
<el-input v-model="form.printerFontTwo" clearable placeholder="请输入第三行字体" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第四行宽度" prop="printerWidthThree">
<el-input v-model="form.printerWidthThree" clearable placeholder="请输入第四行宽度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第四行高度" prop="printerHeightThree">
<el-input v-model="form.printerHeightThree" clearable placeholder="请输入第四行高度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="第四行字体" prop="printerFontThree">
<el-input v-model="form.printerFontThree" clearable placeholder="请输入第四行字体" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="打印数量" prop="printerNumber">
<el-input-number v-model="form.printerNumber" :min="1" :max="999" style="width: 100%" placeholder="请输入打印数量" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="启用状态" prop="isEnabled">
<el-switch
v-model="form.isEnabled"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false" size="default"> </el-button>
<el-button type="primary" @click="submitForm" size="default"> </el-button>
</span>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 10px 20px;
border-top: 1px solid var(--el-border-color-lighter);
}
</style>

View File

@ -0,0 +1,193 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { EquPrinterApi } from '/@/api/admin/EquPrinterApi'
import type { EquPrinterAddInput } from '/@/api/admin/data-contracts'
import PrinterForm from './components/printer-form.vue'
//
const queryParams = reactive({
keyWord: '',
pageNum: 1,
pageSize: 10,
stDate: '',
edDate: ''
})
//
const loading = ref(false)
//
const printerList = ref<any[]>([])
//
const total = ref(0)
//
const dialogVisible = ref(false)
//
const dialogTitle = ref('')
//
const formData = ref<Partial<EquPrinterAddInput>>({})
//
const getList = async () => {
loading.value = true
try {
const res = await new EquPrinterApi().getPage({
keyWord: queryParams.keyWord,
stDate: queryParams.stDate,
edDate: queryParams.edDate,
pageNum: queryParams.pageNum,
pageSize: queryParams.pageSize
})
if (res.success) {
printerList.value = res.data?.list || []
total.value = res.data?.total || 0
}
} catch (error) {
console.error('获取打印机列表失败:', error)
} finally {
loading.value = false
}
}
//
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
//
const resetQuery = () => {
queryParams.keyWord = ''
queryParams.stDate = ''
queryParams.edDate = ''
handleQuery()
}
//
const handleAdd = () => {
dialogTitle.value = '新增打印机'
formData.value = {}
dialogVisible.value = true
}
//
const handleUpdate = (row: any) => {
dialogTitle.value = '修改打印机'
formData.value = { ...row }
dialogVisible.value = true
}
//
const handleDelete = (row: any) => {
ElMessageBox.confirm('确认要删除该打印机吗?', '警告', {
type: 'warning'
}).then(async () => {
try {
const res = await new EquPrinterApi().softDelete({ id: row.id })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除打印机失败:', error)
}
})
}
//
const handleSizeChange = (val: number) => {
queryParams.pageSize = val
getList()
}
//
const handleCurrentChange = (val: number) => {
queryParams.pageNum = val
getList()
}
//
onMounted(() => {
getList()
})
</script>
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="queryParams.keyWord" placeholder="请输入关键词" clearable />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="queryParams.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="queryParams.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery"> 查询 </el-button>
<el-button type="primary" icon="ele-Plus" @click="handleAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table v-loading="loading" :data="printerList" style="width: 100%" border>
<el-table-column label="设备编号" prop="deviceNo" min-width="120" show-overflow-tooltip />
<el-table-column label="资产编号" prop="assetNo" min-width="120" show-overflow-tooltip />
<el-table-column label="IP地址" prop="ip" min-width="120" show-overflow-tooltip />
<el-table-column label="端口号" prop="port" width="100" show-overflow-tooltip />
<el-table-column label="操作" width="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button icon="ele-EditPen" size="small" text type="primary" @click="handleUpdate(row)">编辑</el-button>
<el-button icon="ele-Delete" size="small" text type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
size="small"
background
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 添加或修改对话框 -->
<printer-form
v-model="dialogVisible"
:title="dialogTitle"
:form-data="formData"
@success="getList"
/>
</my-layout>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,491 @@
<template>
<el-dialog
v-model="dialogVisible"
destroy-on-close
:title="title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="1400px"
>
<div class="project-form-container">
<!-- 左侧项目基础信息 -->
<div class="left-panel">
<h4 class="panel-title">基础信息</h4>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" size="default">
<el-form-item label="项目编号" prop="projectNo">
<el-input v-model="form.projectNo" placeholder="请输入项目编号" clearable />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" clearable />
</el-form-item>
<el-form-item label="责任人" prop="principalIds">
<el-select v-model="form.principalIds" multiple placeholder="请选择责任人" style="width: 100%" filterable clearable>
<el-option v-for="item in state.principalOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" />
</el-form-item>
</el-form>
</div>
<!-- 右侧项目人员列表 -->
<div class="right-panel">
<h4 class="panel-title">人员列表</h4>
<div class="user-actions">
<el-button type="primary" size="default" @click="handleAddUser">添加人员</el-button>
</div>
<el-table
:data="form.projectUsers"
style="width: 100%"
size="default"
border
max-height="500"
>
<el-table-column label="用户名称" min-width="120">
<template #default="{ row }">
<span>{{ row.userName || getUserName(row.userId) }}</span>
</template>
</el-table-column>
<el-table-column label="审核权限" width="180">
<template #default="{ row }">
<div class="permission-grid">
<el-checkbox v-model="row.upload" size="default">上传</el-checkbox>
<el-checkbox v-model="row.verify" size="default">审核</el-checkbox>
<el-checkbox v-model="row.review" size="default">下游审核</el-checkbox>
<el-checkbox v-model="row.signature" size="default">签名</el-checkbox>
</div>
</template>
</el-table-column>
<el-table-column label="撤回权限" width="230">
<template #default="{ row }">
<div class="permission-grid">
<el-checkbox v-model="row.revokeUpload" size="default">撤回上传</el-checkbox>
<el-checkbox v-model="row.revokeVerify" size="default">撤回审核</el-checkbox>
<el-checkbox v-model="row.revokeReview" size="default">撤回下游审核</el-checkbox>
<el-checkbox v-model="row.revokeSignature" size="default">撤回签名</el-checkbox>
</div>
</template>
</el-table-column>
<el-table-column prop="limitToDate" label="有效日期" min-width="120">
<template #default="{ row }">
<el-date-picker
v-model="row.limitToDate"
type="date"
placeholder="有效日期"
size="default"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80" fixed="right">
<template #default="{ $index }">
<el-button
type="danger"
size="default"
text
@click="handleRemoveUser($index)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="OnCancel" size="default"> </el-button>
<el-button type="primary" :loading="state.sureLoading" @click="submitForm" size="default"> </el-button>
</span>
</template>
</el-dialog>
<user-select
ref="userSelectRef"
:title="`添加【${form.projectName}】人员`"
multiple
:sure-loading="state.sureLoading"
@sure="onSureUser"
></user-select>
</template>
<script setup lang="ts" name="admin/project/form">
import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { ProjectApi } from '/@/api/admin/ProjectApi'
import { OrgApi } from '/@/api/admin/Org'
import { UserApi } from '/@/api/admin/User'
import { ProjectAddInputAndUpdateInput } from '/@/api/types/projectType'
import { ProjectUser } from '/@/api/types/projectUserType'
import {
UserGetPageOutput,
} from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
const UserSelect = defineAsyncComponent(() => import('/@/views/admin/user/components/user-select.vue'))
const { proxy } = getCurrentInstance() as any
const props = defineProps<{
title: string
}>()
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const userSelectRef = ref()
//
interface ProjectFormData {
id?: number
projectNo: string
projectName: string
principalIds: number[]
projectUsers: Array<{
id?: number
userId: number
userName?: string
groupId?: number
groupName?: string
limitToDate?: string
upload: boolean
revokeUpload: boolean
review: boolean
revokeReview: boolean
signature: boolean
revokeSignature: boolean
verify: boolean
revokeVerify: boolean
}>
status: boolean
}
const form = reactive<ProjectFormData>({
projectNo: '',
projectName: '',
principalIds: [],
projectUsers: [],
status: true
})
const state = reactive({
sureLoading: false,
principalOptions: [] as Array<{ id: number; name: string }>
})
const rules = reactive<FormRules>({
projectNo: [{ required: true, message: '请输入项目编号', trigger: 'blur' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
principalIds: [
{
required: true,
message: '请选择责任人',
trigger: 'change',
validator: (rule: any, value: any, callback: any) => {
if (!Array.isArray(value) || value.length === 0) {
callback(new Error('请选择责任人'))
} else {
callback()
}
}
}
]
})
/** 获取负责人列表 */
const getPrincipalOptions = async () => {
try {
const res = await new UserApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.principalOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.name
})) || []
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
const getUserName = (userId: number) => {
return state.principalOptions.find((a) => a.id === userId)?.name || ''
}
const open = async (id?: number) => {
// loading
dialogVisible.value = true
// IDloading
if (id && id > 0) {
state.sureLoading = true
try {
const res = await new ProjectApi().get({ id }, { loading: false })
if (res?.success && res.data) {
const formData = res.data as any
//
let principalIds: number[] = []
if (Array.isArray(res.data.projectPrincipals)) {
principalIds = res.data.projectPrincipals.map((item: any) => item.userId).filter((id: any) => typeof id === 'number')
}
//
let projectUsers = []
if (Array.isArray(formData.projectUsers)) {
projectUsers = formData.projectUsers
}
//
Object.assign(form, {
id: formData.id,
projectNo: formData.projectNo || '',
projectName: formData.projectName || '',
principalIds: [...principalIds], // 使
projectUsers: [...projectUsers], // 使
status: formData.status !== undefined ? formData.status : true
})
console.log('编辑数据加载:', {
principalIds: form.principalIds,
projectUsers: form.projectUsers
})
}
} catch (error) {
console.error('获取项目详情失败:', error)
ElMessage.error('获取项目详情失败')
} finally {
state.sureLoading = false
}
} else {
//
Object.assign(form, {
id: undefined,
projectNo: '',
projectName: '',
principalIds: [],
projectUsers: [],
status: true
})
}
//
if (state.principalOptions.length === 0) {
await getPrincipalOptions()
}
}
/** 取消 */
const OnCancel = () => {
dialogVisible.value = false
formRef.value?.resetFields()
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
state.sureLoading = true
try {
const api = new ProjectApi()
// API
const submitData = {
id: form.id,
projectNo: form.projectNo,
projectName: form.projectName,
status: form.status,
projectPrincipals: form.principalIds.map((id: number) => ({ userId: id })),
projectUsers: form.projectUsers.length > 0 ? form.projectUsers : []
}
const res = form.id ? await api.update(submitData as any) : await api.add(submitData as any)
if (res.success) {
ElMessage.success(form.id ? '修改成功' : '新增成功')
dialogVisible.value = false
eventBus.emit('refreshProject')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('操作失败,请重试')
} finally {
state.sureLoading = false
}
} else {
ElMessage.warning('存在未填写的必填项')
}
})
}
const handleAddUser = () => {
userSelectRef.value.open()
}
const onSureUser = async (users: UserGetPageOutput[]) => {
if (!(users?.length > 0)) {
userSelectRef.value.close()
return
}
state.sureLoading = true
// id undefined userId
const validUsers = users?.filter((u) => typeof u.id === 'number') ?? []
//
const existingUserIds = form.projectUsers.map((u) => u.userId)
const newUsers = validUsers.filter((u) => !existingUserIds.includes(u.id as number))
const newUserList = newUsers.map((u) => ({
userId: u.id as number, // number
userName: u.userName ?? '',
groupName: '',
limitToDate: '',
upload: false,
revokeUpload: false,
review: false,
revokeReview: false,
signature: false,
revokeSignature: false,
verify: false,
revokeVerify: false
}))
form.projectUsers.push(...newUserList)
state.sureLoading = false
userSelectRef.value.close()
}
/** 删除用户 */
const handleRemoveUser = (index: number) => {
form.projectUsers.splice(index, 1)
}
// principalIds
watch(() => form.principalIds, (newVal, oldVal) => {
console.log('principalIds 变化:', {
新值: newVal,
旧值: oldVal,
类型: Array.isArray(newVal) ? 'Array' : typeof newVal,
长度: Array.isArray(newVal) ? newVal.length : 'N/A'
})
}, { deep: true })
onMounted(async () => {
//
await Promise.all([
getPrincipalOptions()
])
})
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.project-form-container {
display: flex;
gap: 20px;
min-height: 650px;
}
.left-panel {
flex: 0 0 350px;
padding: 20px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background-color: #fafafa;
overflow-y: auto;
}
.right-panel {
flex: 1;
padding: 20px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background-color: #fafafa;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-title {
margin: 0 0 20px 0;
font-size: 16px;
font-weight: 600;
color: #303133;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
}
.user-actions {
margin-bottom: 16px;
flex-shrink: 0;
}
:deep(.el-table) {
border-radius: 6px;
overflow: hidden;
}
:deep(.el-table .el-table__body-wrapper) {
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
:deep(.el-table .el-table__body-wrapper::-webkit-scrollbar) {
width: 8px;
}
:deep(.el-table .el-table__body-wrapper::-webkit-scrollbar-track) {
background: #f1f1f1;
border-radius: 4px;
}
:deep(.el-table .el-table__body-wrapper::-webkit-scrollbar-thumb) {
background: #c1c1c1;
border-radius: 4px;
}
:deep(.el-table .el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
background: #a0a0a0;
}
.permission-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.el-checkbox {
margin-right: 0;
}
}
@media (max-width: 768px) {
:deep(.el-dialog) {
height: 95vh;
width: 95vw !important;
}
.project-form-container {
flex-direction: column;
gap: 16px;
}
.left-panel,
.right-panel {
flex: none;
padding: 16px;
}
.permission-grid {
grid-template-columns: 1fr;
gap: 4px;
}
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<my-layout>
<!-- 查询区 -->
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" :model="state.filter" class="demo-form-inline" @submit.stop.prevent>
<el-form-item label="关键字">
<el-input v-model="state.filter.keyWord" placeholder="请输入关键字" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:project:add'" type="primary" icon="ele-Plus" @click="handleAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格区 -->
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<!-- 可添加工具栏内容如批量操作等 -->
</div>
<el-table v-loading="loading" :data="state.projectList" style="width: 100%" border row-key="projectNo">
<el-table-column prop="projectNo" label="项目编号" />
<el-table-column prop="projectName" label="项目名称" />
<el-table-column prop="createdUserName" label="创建人" />
<el-table-column prop="createdTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:project:update'" icon="ele-EditPen" size="small" text type="primary"
@click="handleEdit(row)">编辑</el-button>
<el-button v-auth="'api:admin:project:soft-delete'" icon="ele-Delete" size="small" text type="danger"
@click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination v-model:current-page="state.pageInput.currentPage" v-model:page-size="state.pageInput.pageSize"
:total="state.total" :page-sizes="[10, 20, 50, 100]" size="small" background @size-change="handleSizeChange"
@current-change="handleCurrentChange" layout="total, sizes, prev, pager, next, jumper" />
</div>
</el-card>
<project-form ref="formRef" :title="state.formTitle" @ok="getList" />
</my-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, defineAsyncComponent } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ProjectApi } from '../../../api/admin/ProjectApi'
import { OrgApi } from '/@/api/admin/Org'
import type { ProjectPageInput, ProjectDto } from '/@/api/types/projectType'
import eventBus from '/@/utils/mitt'
const ProjectForm = defineAsyncComponent(() => import('./components/project-form.vue'))
const loading = ref(false)
const formRef = ref()
const state = reactive({
loading: false,
formTitle: '',
total: 0,
filter: {
keyWord: '',
},
pageInput: {
currentPage: 1,
pageSize: 20,
} as ProjectPageInput,
projectList: [] as Array<ProjectDto>
})
/** 查询列表 */
const getList = async () => {
loading.value = true
state.pageInput.filter = state.filter
try {
const res = await new ProjectApi().getPage(state.pageInput)
state.projectList = res.data?.list || []
state.total = res.data?.total || 0
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
state.pageInput.currentPage = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
state.filter = {
keyWord: '',
}
handleQuery()
}
/** 新增按钮操作 */
const handleAdd = () => {
state.formTitle = '新增项目'
formRef.value?.open()
}
/** 修改按钮操作 */
const handleEdit = (row: ProjectDto) => {
state.formTitle = '编辑项目'
formRef.value?.open(row.id)
}
/** 删除按钮操作 */
const handleDelete = (row: ProjectDto) => {
ElMessageBox.confirm('确认要删除该记录吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await new ProjectApi().softDelete({ id: row.id! })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
/** 每页条数改变 */
const handleSizeChange = (val: number) => {
state.pageInput.pageSize = val
getList()
}
/** 当前页改变 */
const handleCurrentChange = (val: number) => {
state.pageInput.currentPage = val
getList()
}
onMounted(() => {
getList()
eventBus.on('refreshProject', getList)
})
</script>
<style lang="scss" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -0,0 +1,506 @@
<template>
<div class="pump-form">
<el-dialog
v-model="state.isShowDialog"
destroy-on-close
:title="props.title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
<el-form ref="pumpFormRef" :model="state.ruleForm" :rules="state.ruleRules" label-width="120px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo" required>
<el-input v-model="state.ruleForm.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo" required>
<el-input v-model="state.ruleForm.assetNo" placeholder="请输入资产编号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备型号" prop="model" required>
<el-select
v-model="state.ruleForm.model"
placeholder="请选择设备型号"
style="width: 100%"
filterable
allow-create
>
<el-option
v-for="model in state.modelOptions"
:key="model.value"
:label="model.name"
:value="model.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="规格" prop="specification" required>
<el-input v-model="state.ruleForm.specification" placeholder="请输入设备规格" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备负责人" prop="principalId" required>
<el-select
v-model="state.ruleForm.principalId"
placeholder="请选择设备负责人"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="user in state.userOptions"
:key="user.id"
:label="`${user.name} (${user.userName})`"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="泵速策略" prop="configId">
<el-select
v-model="state.ruleForm.configId"
placeholder="请选择泵速策略"
clearable
style="width: 100%"
:loading="state.configLoading"
>
<el-option
v-for="config in state.configOptions"
:key="config.id"
:label="`${config.configName} (${config.isTwoStep ? '两段式' : '三段式'})`"
:value="config.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 技术参数 -->
<div class="form-section">
<div class="section-title">技术参数</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="泵速(g/min)" prop="pumpSpeed" required>
<el-input-number
v-model="state.ruleForm.pumpSpeed"
:min="0"
:precision="2"
style="width: 100%"
placeholder="请输入泵速"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="超时(s)" prop="pumpTimeout" required>
<el-input-number
v-model="state.ruleForm.pumpTimeout"
:min="0"
style="width: 100%"
placeholder="请输入超时时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="泵速系数" prop="speedCoefficient" required>
<el-input-number
v-model="state.ruleForm.speedCoefficient"
:min="0"
:precision="3"
style="width: 100%"
placeholder="请输入泵速系数"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="维护状态" prop="maintenanceFlag">
<el-select
v-model="state.ruleForm.maintenanceFlag"
placeholder="请选择维护状态"
style="width: 100%"
>
<el-option
v-for="status in state.maintenanceStatusOptions"
:key="status.value"
:label="status.label"
:value="status.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 网络配置 -->
<div class="form-section">
<div class="section-title">网络配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="IP地址" prop="ip" required>
<el-input v-model="state.ruleForm.ip" placeholder="请输入IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="端口号" prop="port" required>
<el-input v-model="state.ruleForm.port" placeholder="请输入端口号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="服务名称" prop="serviceName">
<el-input v-model="state.ruleForm.serviceName" placeholder="请输入服务名称" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="state.loading">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="PumpForm">
import { reactive, ref, getCurrentInstance, onMounted } from 'vue'
import { PumpApi } from '/@/api/admin/PumpApi'
import { UserApi } from '/@/api/admin/User'
import { FeedingConfigApi } from '/@/api/admin/FeedingConfigApi'
import { DictApi } from '/@/api/admin/Dict'
import { UserGetPageOutput, PageInputUserGetPageInput, PumpAddInput, PumpUpdateInput, FeedingConfigGetListOutput, DictGetListOutput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
//
const props = defineProps({
title: String,
})
// /
const emit = defineEmits(['refresh'])
//
const pumpFormRef = ref()
const { proxy } = getCurrentInstance() as any
//
type PumpFormData = {
id?: number
deviceNo: string
assetNo: string
model: string
specification: string
pumpSpeed: number
principalId: number | undefined
pumpTimeout: number
maintenanceFlag: number
ip: string
port: string
speedCoefficient: number
serviceName: string
isAirPump: boolean
configId: number | undefined
}
const state = reactive({
isShowDialog: false,
loading: false,
configLoading: false,
ruleForm: {
id: undefined,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
pumpSpeed: 0,
principalId: undefined,
pumpTimeout: 0,
maintenanceFlag: 1,
ip: '',
port: '',
speedCoefficient: 0,
serviceName: '',
isAirPump: false,
configId: undefined,
} as PumpFormData,
userOptions: [] as Array<UserGetPageOutput>,
configOptions: [] as Array<FeedingConfigGetListOutput>,
modelOptions: [] as Array<DictGetListOutput>,
maintenanceStatusOptions: [
{ label: 'Offline', value: 0 },
{ label: 'Idle', value: 1 },
{ label: 'Busy', value: 2 },
{ label: 'Error', value: 3 },
],
ruleRules: {
deviceNo: [
{ required: true, message: '设备编号不能为空', trigger: 'blur' },
{ min: 1, max: 50, message: '设备编号长度应在1-50个字符', trigger: 'blur' }
],
assetNo: [
{ required: true, message: '资产编号不能为空', trigger: 'blur' },
{ min: 1, max: 50, message: '资产编号长度应在1-50个字符', trigger: 'blur' }
],
model: [{ required: true, message: '设备型号不能为空', trigger: 'change' }],
specification: [
{ required: true, message: '规格不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '规格长度应在1-100个字符', trigger: 'blur' }
],
principalId: [{ required: true, message: '设备负责人不能为空', trigger: 'change' }],
pumpSpeed: [{ required: true, message: '泵速不能为空', trigger: 'blur' }],
pumpTimeout: [{ required: true, message: '超时时间不能为空', trigger: 'blur' }],
speedCoefficient: [{ required: true, message: '泵速系数不能为空', trigger: 'blur' }],
ip: [
{ required: true, message: 'IP地址不能为空', trigger: 'blur' },
{ pattern: /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/, message: '请输入正确的IP地址格式', trigger: 'blur' }
],
port: [
{ required: true, message: '端口号不能为空', trigger: 'blur' },
{ pattern: /^([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/, message: '请输入正确的端口号(1-65535)', trigger: 'blur' }
],
},
})
onMounted(() => {
getUserOptions()
getConfigOptions()
getModelOptions()
})
//
const openDialog = async (row?: any) => {
resetForm()
//
if (state.userOptions.length === 0) {
await getUserOptions()
}
//
if (state.configOptions.length === 0) {
await getConfigOptions()
}
//
if (state.modelOptions.length === 0) {
await getModelOptions()
}
if (row && row.id) {
//
try {
const res = await new PumpApi().get({ id: row.id })
if (res?.success && res.data) {
state.ruleForm = {
id: res.data.id,
deviceNo: res.data.deviceNo || '',
assetNo: res.data.assetNo || '',
model: res.data.model || '',
specification: res.data.specification || '',
pumpSpeed: res.data.pumpSpeed || 0,
principalId: res.data.principalId,
pumpTimeout: res.data.pumpTimeout || 0,
maintenanceFlag: res.data.maintenanceFlag ?? 1,
ip: res.data.ip || '',
port: res.data.port || '',
speedCoefficient: res.data.speedCoefficient || 0,
serviceName: res.data.serviceName || '',
isAirPump: false, // false
configId: res.data.configId,
}
}
} catch (error: any) {
if (error.response?.status === 500) {
proxy.$modal.msgError('服务器内部错误,请检查后台服务')
} else {
proxy.$modal.msgError(`获取详情失败: ${error.response?.data?.msg || error.message}`)
}
return
}
}
state.isShowDialog = true
}
//
const closeDialog = () => {
state.isShowDialog = false
resetForm()
}
//
const resetForm = () => {
state.ruleForm = {
id: undefined,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
pumpSpeed: 0,
principalId: undefined,
pumpTimeout: 0,
maintenanceFlag: 1,
ip: '',
port: '',
speedCoefficient: 0,
serviceName: '',
isAirPump: false,
configId: undefined,
}
}
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000,
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getConfigOptions = async () => {
state.configLoading = true
try {
const res = await new FeedingConfigApi().getList()
if (res?.success && res.data) {
state.configOptions = res.data
}
} catch (error) {
//
} finally {
state.configLoading = false
}
}
//
const getModelOptions = async () => {
try {
const res = await new DictApi().getList(['PUMPTYPE'])
if (res?.success && res.data) {
// APIkey pumptype
state.modelOptions = res.data['pumptype'] || []
}
} catch (error) {
//
}
}
//
const onCancel = () => {
closeDialog()
}
//
const onSubmit = () => {
pumpFormRef.value.validate(async (valid: boolean) => {
if (!valid) return false
state.loading = true
try {
let res = {} as any
// isAirPump false
const formData = { ...state.ruleForm, isAirPump: false }
if (formData.id) {
res = await new PumpApi().update(formData as any, { showSuccessMessage: true })
} else {
res = await new PumpApi().add(formData as any, { showSuccessMessage: true })
}
if (res?.success) {
closeDialog()
emit('refresh')
eventBus.emit('refreshPump')
}
} catch (error: any) {
proxy.$modal.msgError('操作失败')
} finally {
state.loading = false
}
})
}
//
defineExpose({
openDialog,
closeDialog,
})
</script>
<style scoped lang="scss">
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.queryForm.keyWord" placeholder="设备编号、资产编号" @keyup.enter="Query" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.queryForm.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.queryForm.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button v-if="auth('api:admin:equ-pump:get-page')" @click="Query" type="primary">
<SvgIcon name="ele-Search" />查询
</el-button>
<el-button v-auth="'api:admin:equ-pump:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table
v-loading="state.loading"
:data="state.tableData"
stripe
border
style="width: 100%"
>
<el-table-column prop="deviceNo" label="设备编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="model" label="设备型号" min-width="120" show-overflow-tooltip />
<el-table-column prop="specification" label="规格" min-width="120" show-overflow-tooltip />
<el-table-column prop="pumpSpeed" label="泵速(g/min)" width="110" align="center" />
<el-table-column prop="principalId" label="负责人" width="120" align="center">
<template #default="{ row }">
<span>{{ getPrincipalName(row.principalId) }}</span>
</template>
</el-table-column>
<el-table-column prop="maintenanceFlag" label="维护状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getMaintenanceStatusType(row.maintenanceFlag)">
{{ getMaintenanceStatusText(row.maintenanceFlag) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }">
<el-button
size="small"
type="primary"
@click="onEdit(row)"
v-auth="'api:admin:equ-pump:update'"
>
编辑
</el-button>
<el-button
size="small"
type="danger"
@click="onDelete(row)"
v-auth="'api:admin:equ-pump:soft-delete'"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.queryForm.currentPage"
v-model:page-size="state.queryForm.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<pump-form ref="pumpFormRef" :title="state.title" @refresh="Query"></pump-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/pump">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { ElMessageBox } from 'element-plus'
import { PumpApi } from '/@/api/admin/PumpApi'
import { UserApi } from '/@/api/admin/User'
import { PumpGetPageOutput, PageInputPumpGetPageInput, UserGetPageOutput, PageInputUserGetPageInput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
//
const PumpForm = defineAsyncComponent(() => import('./components/pump-form.vue'))
//
const pumpFormRef = ref()
const { proxy } = getCurrentInstance() as any
const state = reactive({
loading: false,
title: '',
filter: {
keyWord: '',
stDate: '',
edDate: '',
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputPumpGetPageInput,
queryForm: {
keyWord: '',
stDate: '',
edDate: '',
currentPage: 1,
pageSize: 20,
},
tableData: [] as Array<PumpGetPageOutput>,
userOptions: [] as Array<UserGetPageOutput>,
showQuery: true,
showPumpList: true,
})
onMounted(() => {
getUserOptions()
Query()
eventBus.off('refreshPump')
eventBus.on('refreshPump' , () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshPump' as keyof MittType<any>)
})
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000,
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getPrincipalName = (principalId?: number) => {
if (!principalId) return '-'
const user = state.userOptions.find(u => u.id === principalId)
return user ? `${user.name} (${user.userName})` : '-'
}
//
const getMaintenanceStatusText = (status?: number) => {
switch (status) {
case 0:
return 'Offline'
case 1:
return 'Idle'
case 2:
return 'Busy'
case 3:
return 'Error'
default:
return '未知'
}
}
//
const getMaintenanceStatusType = (status?: number) => {
switch (status) {
case 0:
return 'info'
case 1:
return 'success'
case 2:
return 'warning'
case 3:
return 'danger'
default:
return ''
}
}
//
const Query = async () => {
state.loading = true
try {
const queryParams = {
currentPage: state.queryForm.currentPage,
pageSize: state.queryForm.pageSize,
filter: {
keyWord: state.queryForm.keyWord || null,
stDate: state.queryForm.stDate || null,
edDate: state.queryForm.edDate || null,
isAirPump: false, // false
}
} as PageInputPumpGetPageInput
const res = await new PumpApi().getPage(queryParams)
if (res?.success) {
state.tableData = res.data?.list ?? []
state.total = res.data?.total ?? 0
}
} catch (error: any) {
proxy.$modal.msgError(`查询失败: ${error.response?.data?.msg || error.message}`)
} finally {
state.loading = false
}
}
//
const onReset = () => {
state.queryForm = {
keyWord: '',
stDate: '',
edDate: '',
currentPage: 1,
pageSize: 20,
}
Query()
}
//
const onSizeChange = (size: number) => {
state.queryForm.pageSize = size
Query()
}
//
const onCurrentChange = (page: number) => {
state.queryForm.currentPage = page
Query()
}
//
const onAdd = () => {
state.title = '新增外置泵'
pumpFormRef.value.openDialog()
}
//
const onEdit = (row: PumpGetPageOutput) => {
state.title = '编辑外置泵'
pumpFormRef.value.openDialog(row)
}
//
const onDelete = (row: PumpGetPageOutput) => {
ElMessageBox.confirm(`确定要删除外置泵【${row.deviceNo}】吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
const res = await new PumpApi().softDelete({ id: row.id! })
if (res?.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error: any) {
proxy.$modal.msgError(`删除失败: ${error.response?.data?.msg || error.message}`)
}
})
.catch(() => {})
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<el-dialog v-model="dialogVisible" title="报警设置" width="1200px" :close-on-click-modal="false" :close-on-press-escape="false" destroy-on-close draggable class="alarm-dialog-high">
<div style="margin-bottom: 16px; display: flex; justify-content: flex-end; gap: 8px;">
<el-button type="primary" @click="setAllAlarmStatus(true)">一键开启</el-button>
<el-button type="warning" @click="setAllAlarmStatus(false)">一键关闭</el-button>
</div>
<div class="alarm-table-wrapper">
<el-table :data="state.alarmSettingList" border style="width: 100%;" max-height="550px" :header-cell-style="{background:'#fafafa'}">
<el-table-column label="序号" width="60" align="center">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="epDefName" label="在线数据" min-width="120" align="center" />
<el-table-column prop="epDefAlias" label="OPC名称" min-width="120" align="center">
<template #default="{ row }">
<template v-if="row.needConfig">
<el-select v-model="row.epDefAlias" placeholder="-" style="width: 100%" filterable>
<el-option v-for="item in opcNameOptions" :key="item" :label="item" :value="item" />
</el-select>
</template>
<template v-else>
-
</template>
</template>
</el-table-column>
<el-table-column prop="warningLowerLimit" label="报警下限值" min-width="100" align="center">
<template #default="{ row }">
<el-input v-model="row.warningLowerLimit" size="small" />
</template>
</el-table-column>
<el-table-column prop="warningLowerBuffer" label="下限延时/s" min-width="100" align="center">
<template #default="{ row }">
<el-input v-model="row.warningLowerBuffer" size="small" />
</template>
</el-table-column>
<el-table-column prop="warningUpperLimit" label="报警上限值" min-width="100" align="center">
<template #default="{ row }">
<el-input v-model="row.warningUpperLimit" size="small" />
</template>
</el-table-column>
<el-table-column prop="warningUpperBuffer" label="上限延时/s" min-width="100" align="center">
<template #default="{ row }">
<el-input v-model="row.warningUpperBuffer" size="small" />
</template>
</el-table-column>
<el-table-column prop="unit" label="计量单位" min-width="80" align="center">
<template #default="{ row }">
{{ row.epDefName === 'Pressure' ? state.pressureUnit : row.unit }}
</template>
</el-table-column>
<el-table-column prop="isEnable" label="报警状态" min-width="100" align="center">
<template #default="{ row }">
<el-switch v-model="row.isEnable" />
</template>
</el-table-column>
</el-table>
</div>
<div class="alarm-dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveAlarmConfig">保存</el-button>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { ReactorEPSettingApi } from '/@/api/admin/ReactorEPSettingApi'
import { ReactorEPSetting } from '/@/api/types/reactorEPSetting'
import { ReactorDto } from '/@/api/types/reactorType'
defineOptions({ name: 'AlarmSettingForm' })
const dialogVisible = ref(false)
const state = reactive({
alarmSettingList: [] as ReactorEPSetting[],
pressureUnit: ''
})
const opcNameOptions = ['MFC1', 'MFC2', 'MFC3', 'MFC4', 'MFC5', 'MFC6']
function setAllAlarmStatus(status: boolean) {
state.alarmSettingList.forEach(item => item.isEnable = status)
}
async function saveAlarmConfig() {
const res = await new ReactorEPSettingApi().batchUpdate(state.alarmSettingList)
if (res.success) {
ElMessage.success('报警配置已保存')
dialogVisible.value = false
}
}
async function open(reactor: ReactorDto) {
console.log("Reactor", reactor.pressureUnit)
await getAlarmSettingList(reactor.id!)
state.pressureUnit = reactor.pressureUnit!
dialogVisible.value = true
}
const getAlarmSettingList = async (id: number) => {
const res = await new ReactorEPSettingApi().getListByEquReactor({ equReactorId: id })
state.alarmSettingList = res.data
}
defineExpose({ open })
</script>
<style scoped>
.alarm-table-wrapper {
max-height: 600px;
overflow: auto;
}
.alarm-dialog-footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 16px 0 0 0;
text-align: right;
z-index: 10;
}
</style>

View File

@ -0,0 +1,823 @@
<template>
<el-dialog v-model="dialogVisible" :title="form.id ? '编辑反应器' : '新增反应器'" width="900px" :close-on-click-modal="false"
:close-on-press-escape="false" destroy-on-close draggable class="reactor-form-dialog">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="reactor-form">
<el-tabs v-model="activeTab" class="reactor-tabs">
<el-tab-pane label="基本信息" name="basic">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="form.deviceNo" placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="反应器名称" prop="productID">
<el-input v-model="form.productID" placeholder="请输入反应器名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="资产编号" prop="assetNo">
<el-input v-model="form.assetNo" placeholder="请输入资产编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备型号" prop="model">
<el-select v-model="form.model" placeholder="请选择设备型号" style="width: 100%" filterable allow-create>
<el-option v-for="item in state.deviceModelOptions" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格" prop="specification">
<el-input v-model="form.specification" placeholder="请输入规格" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="容量(g)" prop="capacity">
<el-input-number v-model="form.capacity" :min="0" :step="1" style="width: 100%" placeholder="请输入容量" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="房间" prop="roomID">
<el-select v-model="form.roomID" placeholder="请选择房间" style="width: 100%">
<el-option v-for="item in state.roomOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备负责人" prop="principalId">
<el-select v-model="form.principalId" placeholder="请选择设备负责人" style="width: 100%">
<el-option v-for="item in state.principalOptions" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="压力单位" prop="pressureUnit">
<el-select v-model="form.pressureUnit" placeholder="请选择压力单位" class="w100">
<el-option v-for="item in state.pressureUnitOptions" :key="item.label" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备状态" prop="deviceStatus">
<el-tag>
{{ getDeviceStatusDesc(form.deviceStatus ?? 1) }}
</el-tag>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="维护标记">
<el-switch v-model="form.maintenanceFlag" active-text="维护中" inactive-text="正常" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="维修完成时间" v-if="form.maintenanceFlag">
<el-date-picker v-model="form.maintenanceTime" type="date" placeholder="预计完成时间" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" style="width: 180px" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="设备配置" name="config">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="反应器地秤" prop="equScaleId">
<el-select v-model="form.equScaleId" placeholder="请选择反应器地秤" class="w100">
<el-option v-for="item in state.equScaleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="补料地秤" prop="feedingScaleId">
<el-select v-model="form.feedingScaleId" placeholder="请选择补料地秤" class="w100">
<el-option v-for="item in state.feedingScaleOptions" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="继电器" prop="relayID">
<el-select v-model="form.relayID" placeholder="请选择继电器" class="w100">
<el-option v-for="item in state.relayOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报警器" prop="equAlarmId">
<el-select v-model="form.equAlarmId" placeholder="请选择报警器" class="w100">
<el-option v-for="item in state.equAlarmOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="警告下限(g)" prop="warningLowerLimit">
<el-input-number v-model="form.warningLowerLimit" :min="0" :step="1" style="width: 100%"
placeholder="请输入警告下限" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="警告上限(g)" prop="warningUpperLimit">
<el-input-number v-model="form.warningUpperLimit" :min="0" :step="1" style="width: 100%"
placeholder="请输入警告上限" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="OPC IP" prop="opcip">
<el-input v-model="form.opcip" placeholder="请输入OPC IP" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="OPC 端口" prop="opcPort">
<el-input v-model="form.opcPort" placeholder="请输入OPC 端口" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="需要外置泵" prop="isExternalPump">
<el-switch v-model="form.isExternalPump" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="泵配置" name="pumpConfig">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="泵1速度" prop="pump1.speed">
<el-input-number v-model="form.pump1.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵1速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵1配置" prop="pump1.configId">
<el-select
v-model="form.pump1.configId"
placeholder="请选择泵1配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵2速度" prop="pump2.speed">
<el-input-number v-model="form.pump2.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵2速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵2配置" prop="pump2.configId">
<el-select
v-model="form.pump2.configId"
placeholder="请选择泵2配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵3速度" prop="pump3.speed">
<el-input-number v-model="form.pump3.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵3速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵3配置" prop="pump3.configId">
<el-select
v-model="form.pump3.configId"
placeholder="请选择泵3配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵4速度" prop="pump4.speed">
<el-input-number v-model="form.pump4.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵4速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵4配置" prop="pump4.configId">
<el-select
v-model="form.pump4.configId"
placeholder="请选择泵4配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵5速度" prop="pump5.speed">
<el-input-number v-model="form.pump5.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵5速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵5配置" prop="pump5.configId">
<el-select
v-model="form.pump5.configId"
placeholder="请选择泵5配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵6速度" prop="pump6.speed">
<el-input-number v-model="form.pump6.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵6速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵6配置" prop="pump6.configId">
<el-select
v-model="form.pump6.configId"
placeholder="请选择泵6配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵7速度" prop="pump7.speed">
<el-input-number v-model="form.pump7.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵7速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵7配置" prop="pump7.configId">
<el-select
v-model="form.pump7.configId"
placeholder="请选择泵7配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵8速度" prop="pump8.speed">
<el-input-number v-model="form.pump8.speed" :min="0" :step="1" style="width: 100%"
placeholder="请输入泵8速度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="泵8配置" prop="pump8.configId">
<el-select
v-model="form.pump8.configId"
placeholder="请选择泵8配置"
style="width: 100%"
filterable
>
<el-option
v-for="item in state.pumpConfigOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="OnCancel" size="default"> </el-button>
<el-button type="primary" :loading="state.sureLoading" @click="submitForm" size="default"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="admin/reactor/form">
import { ref, reactive, onMounted, watch, getCurrentInstance } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { ReactorApi } from '../../../../api/admin/ReactorApi'
import { RoomApi } from '/@/api/admin/Room'
import { UserApi } from '/@/api/admin/User'
import { DictApi } from '/@/api/admin/Dict'
import { UspscaleApi } from '/@/api/admin/UspscaleApi'
import { UspRelayApi } from '/@/api/admin/UspRelayApi'
import { AlarmApi } from '/@/api/admin/AlarmApi'
import { UspFeedingConfigApi } from '/@/api/admin/UspFeedingConfigApi'
import {
DeviceStatusEnum,
MaintenanceFlagEnum,
ReactorAddInputAndUpdateInput,
PumpConfig,
defaultPumpConfig,
ReactorTypeEnumItem
} from '/@/api/types/reactorType'
import { toOptionsByValue } from '/@/utils/enum'
import { EnumPressureUnit } from '/@/api/admin/enum-contracts'
import eventBus from '/@/utils/mitt'
const { proxy } = getCurrentInstance() as any
const props = defineProps<{
title: string
}>()
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const activeTab = ref('basic')
const form = reactive<ReactorAddInputAndUpdateInput>({
sort: 0,
deviceNo: '',
assetNo: '',
productID: '',
model: '',
specification: '',
capacity: 0,
roomID: undefined,
principalId: undefined,
deviceStatus: 1,
maintenanceFlag: undefined,
maintenanceTime: '',
pressureUnit: '',
equScaleId: undefined,
feedingScaleId: undefined,
relayID: undefined,
equAlarmId: undefined,
warningLowerLimit: 0,
warningUpperLimit: 0,
errorCode: '',
isExternalPump: true,
opcip: '',
opcPort: '',
pump1: { ...defaultPumpConfig },
pump2: { ...defaultPumpConfig },
pump3: { ...defaultPumpConfig },
pump4: { ...defaultPumpConfig },
pump5: { ...defaultPumpConfig },
pump6: { ...defaultPumpConfig },
pump7: { ...defaultPumpConfig },
pump8: { ...defaultPumpConfig }
})
const state = reactive({
sureLoading: false,
roomOptions: [] as Array<{ id: number; name: string }>,
principalOptions: [] as Array<{ id: number; name: string }>,
deviceStatusOptions: [] as ReactorTypeEnumItem[],
deviceModelOptions: [] as Array<{ id: string; name: string }>,
pressureUnitOptions: toOptionsByValue(EnumPressureUnit),
equScaleOptions: [] as Array<{ id: number; name: string }>,
feedingScaleOptions: [] as Array<{ id: number; name: string }>,
relayOptions: [] as Array<{ id: number; name: string }>,
equAlarmOptions: [] as Array<{ id: number; name: string }>,
pumpConfigOptions: [] as Array<{ id: number; name: string }>,
})
const rules = reactive<FormRules>({
deviceNo: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
productID: [{ required: true, message: '请输入反应器名称', trigger: 'blur' }],
model: [{ required: true, message: '请选择设备型号', trigger: 'change' }],
assetNo: [{ required: true, message: '请输入资产编号', trigger: 'blur' }],
specification: [{ required: false, message: '请输入规格', trigger: 'blur' }],
capacity: [{ required: true, message: '请输入容量', trigger: 'blur' }],
roomID: [{ required: false, message: '请选择房间', trigger: 'change' }],
principalId: [{ required: false, message: '请选择设备负责人', trigger: 'change' }],
deviceStatus: [{ required: false, message: '请选择设备状态', trigger: 'change' }],
pressureUnit: [{ required: true, message: '请选择压力单位', trigger: 'change' }],
equScaleId: [{ required: true, message: '请选择反应器地秤', trigger: 'change' }],
feedingScaleId: [{ required: false, message: '请输入补料秤ID', trigger: 'blur' }],
relayID: [{ required: false, message: '请输入继电器ID', trigger: 'blur' }],
equAlarmId: [{ required: false, message: '请输入报警器ID', trigger: 'blur' }],
warningLowerLimit: [{ required: true, message: '请输入警告下限', trigger: 'blur' }],
warningUpperLimit: [{ required: true, message: '请输入警告上限', trigger: 'blur' }],
'pump1.speed': [
{ required: true, message: '请输入泵1速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵1速度不能小于0', trigger: 'blur' }
],
'pump1.configId': [
{ required: true, message: '请输入泵1配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵1配置ID不能小于0', trigger: 'blur' }
],
'pump2.speed': [
{ required: true, message: '请输入泵2速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵2速度不能小于0', trigger: 'blur' }
],
'pump2.configId': [
{ required: true, message: '请输入泵2配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵2配置ID不能小于0', trigger: 'blur' }
],
'pump3.speed': [
{ required: true, message: '请输入泵3速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵3速度不能小于0', trigger: 'blur' }
],
'pump3.configId': [
{ required: true, message: '请输入泵3配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵3配置ID不能小于0', trigger: 'blur' }
],
'pump4.speed': [
{ required: true, message: '请输入泵4速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵4速度不能小于0', trigger: 'blur' }
],
'pump4.configId': [
{ required: true, message: '请输入泵4配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵4配置ID不能小于0', trigger: 'blur' }
],
'pump5.speed': [
{ required: true, message: '请输入泵5速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵5速度不能小于0', trigger: 'blur' }
],
'pump5.configId': [
{ required: true, message: '请输入泵5配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵5配置ID不能小于0', trigger: 'blur' }
],
'pump6.speed': [
{ required: true, message: '请输入泵6速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵6速度不能小于0', trigger: 'blur' }
],
'pump6.configId': [
{ required: true, message: '请输入泵6配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵6配置ID不能小于0', trigger: 'blur' }
],
'pump7.speed': [
{ required: true, message: '请输入泵7速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵7速度不能小于0', trigger: 'blur' }
],
'pump7.configId': [
{ required: true, message: '请输入泵7配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵7配置ID不能小于0', trigger: 'blur' }
],
'pump8.speed': [
{ required: true, message: '请输入泵8速度', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵8速度不能小于0', trigger: 'blur' }
],
'pump8.configId': [
{ required: true, message: '请输入泵8配置ID', trigger: 'blur' },
{ type: 'number', min: 0, message: '泵8配置ID不能小于0', trigger: 'blur' }
]
})
/** 获取设备状态枚举列表 */
const getDeviceStatusOptions = async () => {
try {
const res = await new ReactorApi().getDeviceStatusEnumList()
state.deviceStatusOptions = res.data ?? []
} catch (error) {
console.error('获取设备状态枚举列表失败:', error)
}
}
/** 获取房间列表 */
const getRoomOptions = async () => {
try {
const res = await new RoomApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.roomOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.roomName
})) || []
} catch (error) {
console.error('获取房间列表失败:', error)
}
}
/** 获取负责人列表 */
const getPrincipalOptions = async () => {
try {
const res = await new UserApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.principalOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.userName
})) || []
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
const getDeviceModelOptions = async () => {
try {
const res = await new DictApi().getList(['ReactorModel'])
state.deviceModelOptions = res.data?.reactorModel?.map((item: any) => ({
id: item.value,
name: item.name
})) || []
}
catch (error) {
console.error('获取设备型号列表失败:', error)
}
}
const getEquAlarmOptions = async () => {
try {
const res = await new AlarmApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.equAlarmOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.deviceNo
})) || []
} catch (error) {
console.error('获取报警器列表失败:', error)
}
}
const getRelayOptions = async () => {
try {
const res = await new UspRelayApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.relayOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.deviceNo
})) || []
}
catch (error) {
console.error('获取继电器列表失败:', error)
}
}
const getEquScaleOptions = async () => {
try {
const res = await new UspscaleApi().getPage({ currentPage: 1,
pageSize: 1000,
filter: {}
})
state.equScaleOptions = res.data?.list?.filter((item: any) => item.isFeedingScale == false).map((item: any) => ({
id: item.id,
name: item.deviceNo
})) || []
state.feedingScaleOptions = res.data?.list?.filter((item: any) => item.isFeedingScale == true).map((item: any) => ({
id: item.id,
name: item.deviceNo
})) || []
}
catch (error) {
console.error('获取地秤列表失败:', error)
}
}
const getPumpConfigOptions = async () => {
try {
const res = await new UspFeedingConfigApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {
}
})
state.pumpConfigOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.configName
})) || []
}
catch (error) {
console.error('获取泵配置列表失败:', error)
}
}
const getDeviceStatusDesc = (status: DeviceStatusEnum) => {
return state.deviceStatusOptions.find(item => item.value === status)?.label || ''
}
/** 打开弹窗 */
const open = async (id?: number) => {
proxy.$modal.loading()
if (id && id > 0) {
const res = await new ReactorApi().get({ id }, { loading: true })
if (res?.success && res.data) {
const formData = res.data as unknown as ReactorAddInputAndUpdateInput
Object.assign(form, formData)
}
} else {
Object.assign(form, {
sort: 0,
deviceNo: '',
assetNo: '',
productID: '',
model: '',
specification: '',
capacity: 0,
roomID: undefined,
principalId: undefined,
deviceStatus: 1,
maintenanceFlag: undefined,
maintenanceTime: '',
pressureUnit: '',
equScaleId: undefined,
feedingScaleId: undefined,
relayID: undefined,
equAlarmId: undefined,
warningLowerLimit: 0,
warningUpperLimit: 0,
errorCode: '',
isExternalPump: true,
opcip: '',
opcPort: '',
pump1: { ...defaultPumpConfig },
pump2: { ...defaultPumpConfig },
pump3: { ...defaultPumpConfig },
pump4: { ...defaultPumpConfig },
pump5: { ...defaultPumpConfig },
pump6: { ...defaultPumpConfig },
pump7: { ...defaultPumpConfig },
pump8: { ...defaultPumpConfig },
})
}
proxy.$modal.closeLoading()
dialogVisible.value = true
await Promise.all([
getDeviceStatusOptions(),
getRoomOptions(),
getPrincipalOptions(),
getDeviceModelOptions(),
getEquScaleOptions(),
getRelayOptions(),
getEquAlarmOptions(),
getPumpConfigOptions()
])
}
/** 取消 */
const OnCancel = () => {
dialogVisible.value = false
formRef.value?.resetFields()
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
state.sureLoading = true //
try {
const api = new ReactorApi()
const res = form.id ? await api.update(form) : await api.add(form)
if (res.success) {
ElMessage.success(form.id ? '修改成功' : '新增成功')
dialogVisible.value = false
eventBus.emit('refreshReactor')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('操作失败,请重试')
} finally {
state.sureLoading = false //
}
}
else {
ElMessage.warning('存在未填写的必填项')
}
})
}
// maintenanceFlag
watch(() => form.maintenanceFlag, (newValue) => {
if (newValue != undefined && newValue == 0) {
form.maintenanceTime = ''
}
})
onMounted(() => {
getDeviceStatusOptions()
getRoomOptions()
getPrincipalOptions()
getDeviceModelOptions()
getEquScaleOptions()
getRelayOptions()
getEquAlarmOptions()
getPumpConfigOptions()
})
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.reactor-form-dialog {
:deep(.el-dialog__body) {
padding: 20px 30px;
}
}
.reactor-form {
.el-form-item {
margin-bottom: 22px;
}
}
.reactor-tabs {
:deep(.el-tabs__content) {
padding: 20px 0;
}
:deep(.el-tabs__nav) {
margin-bottom: 20px;
}
}
:deep(.el-input-number) {
width: 100%;
.el-input__wrapper {
padding-left: 11px;
padding-right: 11px;
}
}
:deep(.el-select) {
width: 100%;
}
.pump-config-section {
margin-top: 20px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
h3 {
margin-bottom: 20px;
font-size: 16px;
color: #303133;
}
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" :label-position="'left'" @submit.stop.prevent>
<el-form-item label="关键字" prop="keyWord">
<el-input v-model="state.filter.keyWord" placeholder="请输入设备编号/资产编号" clearable style="width: 300px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="房间" prop="roomId">
<el-select v-model="state.filter.roomId" placeholder="请选择房间" clearable style="width: 200px">
<el-option v-for="item in state.roomOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker v-model="state.filter.stDate" type="datetime" placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker v-model="state.filter.edDate" type="datetime" placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery">查询</el-button>
<el-button v-auth="'api:admin:equ-reactor:add'" type="primary" icon="ele-Plus"
@click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table v-loading="loading" :data="state.reactorList" row-key="id" style="width: 100%" border>
<el-table-column prop="deviceNo" label="设备编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="model" label="设备型号" width="100">
<template #default="{ row }">
{{ getDeviceModelDesc(row.model) }}
</template>
</el-table-column>
<el-table-column prop="specification" label="规格" min-width="120" show-overflow-tooltip />
<el-table-column prop="capacity" label="容量(g)" min-width="100" show-overflow-tooltip />
<el-table-column prop="roomId" label="房间" width="100">
<template #default="{ row }">
{{ getRoomName(row.roomID) }}
</template>
</el-table-column>
<el-table-column prop="roomId" label="负责人" width="200">
<template #default="{ row }">
{{ getPrincipalName(row.principalId) }}
</template>
</el-table-column>
<el-table-column prop="deviceStatus" label="设备状态" width="100">
<template #default="{ row }">
<el-tag>
{{ getDeviceStatusDesc(row.deviceStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdTime" label="创建时间" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="240" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:equ-reactor:update'" icon="ele-EditPen" size="small" text type="primary"
@click="handleEdit(row)">编辑</el-button>
<el-button icon="ele-Bell" size="small" text type="warning" @click="openAlarmSetting(row)">报警设置</el-button>
<el-button v-auth="'api:admin:equ-reactor:soft-delete'" icon="ele-Delete" size="small" text type="danger"
@click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination v-model:current-page="state.pageInput.currentPage" v-model:page-size="state.pageInput.pageSize"
:total="state.total" :page-sizes="[10, 20, 50, 100]" size="small" background @size-change="handleSizeChange"
@current-change="handleCurrentChange" layout="total, sizes, prev, pager, next, jumper" />
</div>
</el-card>
<reactor-form ref="formRef" :title="state.formTitle" @ok="getList" />
<alarm-setting-form ref="alarmSettingRef" />
</my-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount, defineAsyncComponent } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ReactorApi } from '../../../api/admin/ReactorApi'
import { RoomApi } from '/@/api/admin/Room'
import { UserApi } from '/@/api/admin/User'
import { DictApi } from '/@/api/admin/Dict'
import eventBus from '/@/utils/mitt'
import type {
ReactorPageInput,
ReactorDto,
ReactorTypeEnumItem
} from '/@/api/types/reactorType'
const AlarmSettingForm = defineAsyncComponent(() => import('./components/alarm-setting-form.vue'))
const ReactorForm = defineAsyncComponent(() => import('./components/reactor-form.vue'))
const loading = ref(false)
const formRef = ref()
const alarmSettingRef = ref()
const state = reactive({
loading: false,
formTitle: '',
total: 0,
filter: {
keyWord: '',
stDate: '',
edDate: '',
roomId: undefined as number | undefined
},
pageInput: {
currentPage: 1,
pageSize: 20,
} as ReactorPageInput,
reactorList: [] as Array<ReactorDto>,
roomOptions: [] as Array<{ id: number; name: string }>,
principalOptions: [] as Array<{ id: number; name: string }>,
deviceStatusOptions: [] as ReactorTypeEnumItem[],
deviceModelOptions: [] as Array<{ id: string; name: string }>,
})
/** 查询列表 */
const getList = async () => {
loading.value = true
state.pageInput.filter = state.filter
try {
console.log(state.filter);
const res = await new ReactorApi().getPage(state.pageInput)
state.reactorList = res.data?.list || []
state.total = res.data?.total || 0
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
state.pageInput.currentPage = 1
getList()
}
/** 新增按钮操作 */
const handleAdd = () => {
state.formTitle = '新增反应器'
formRef.value?.open()
}
/** 修改按钮操作 */
const handleEdit = (row: ReactorDto) => {
state.formTitle = '编辑反应器'
formRef.value?.open(row.id)
}
/** 删除按钮操作 */
const handleDelete = (row: ReactorDto) => {
ElMessageBox.confirm('确认要删除该记录吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await new ReactorApi().softDelete({ id: row.id! })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
/** 每页条数改变 */
const handleSizeChange = (val: number) => {
state.pageInput.pageSize = val
getList()
}
/** 当前页改变 */
const handleCurrentChange = (val: number) => {
state.pageInput.currentPage = val
getList()
}
/** 获取设备状态枚举列表 */
const getDeviceStatusOptions = async () => {
try {
const res = await new ReactorApi().getDeviceStatusEnumList()
state.deviceStatusOptions = res.data ?? []
} catch (error) {
console.error('获取设备状态枚举列表失败:', error)
}
}
/** 获取房间列表 */
const getRoomOptions = async () => {
try {
const res = await new RoomApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.roomOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.roomName
})) || []
} catch (error) {
console.error('获取房间列表失败:', error)
}
}
/** 获取负责人列表 */
const getPrincipalOptions = async () => {
try {
const res = await new UserApi().getPage({
currentPage: 1,
pageSize: 1000,
filter: {}
})
state.principalOptions = res.data?.list?.map((item: any) => ({
id: item.id,
name: item.userName
})) || []
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
const getDeviceModelOptions = async () => {
try {
const res = await new DictApi().getList(['ReactorModel'])
state.deviceModelOptions = res.data?.reactorModel?.map((item: any) => ({
id: item.value,
name: item.name
})) || []
}
catch (error) {
console.error('获取设备型号列表失败:', error)
}
}
/** 获取设备型号描述 */
const getDeviceModelDesc = (value: string) => {
const item = state.deviceModelOptions.find(item => item.id === value)
return item?.name || '未知'
}
/** 获取设备状态描述 */
const getDeviceStatusDesc = (value: number) => {
const item = state.deviceStatusOptions.find(item => item.value === value)
return item?.label || '未知'
}
/** 获取设备状态描述 */
const getRoomName = (value: number) => {
const item = state.roomOptions.find(item => item.id === value)
return item?.name || '未知'
}
/** 获取负责人名称 */
const getPrincipalName = (value: number) => {
const item = state.principalOptions.find(item => item.id === value)
return item?.name || '未知'
}
function openAlarmSetting(row: ReactorDto) {
alarmSettingRef.value?.open(row)
}
onMounted(() => {
getRoomOptions()
getPrincipalOptions()
getDeviceStatusOptions()
getDeviceModelOptions()
getList()
eventBus.on('refreshReactor', getList)
})
onBeforeMount(() => {
eventBus.off('refreshReactor')
})
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-flex {
display: flex;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,285 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="900px"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
append-to-body
destroy-on-close
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
size="default"
v-loading="loading"
>
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo">
<el-input v-model="formData.deviceNo" clearable placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo">
<el-input v-model="formData.assetNo" clearable placeholder="请输入资产编号" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 网络配置 -->
<div class="form-section">
<div class="section-title">网络配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="IP地址" prop="ip">
<el-input v-model="formData.ip" clearable placeholder="请输入IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="端口号" prop="port">
<el-input v-model="formData.port" clearable placeholder="请输入端口号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="服务名称" prop="serviceName">
<el-input v-model="formData.serviceName" clearable placeholder="请输入服务名称" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 设备配置 -->
<div class="form-section">
<div class="section-title">设备配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="状态" prop="enabled">
<el-switch
v-model="formData.enabled"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false" size="default"> </el-button>
<el-button type="primary" @click="handleSubmit" size="default" :loading="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { UspRelayApi } from '/@/api/admin/UspRelayApi'
import type { RelayGetOutput, RelayAddInput, RelayUpdateInput } from '/@/api/admin/data-contracts'
const props = defineProps<{
modelValue: boolean
title: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'refresh'): void
}>()
//
const formRef = ref<FormInstance>()
//
const formData = reactive<RelayGetOutput>({
deviceNo: '',
assetNo: '',
ip: '',
port: '',
serviceName: '',
enabled: true
})
//
const rules = {
deviceNo: [
{ required: true, message: '请输入设备编号', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: ['blur', 'change'] }
],
assetNo: [
{ required: true, message: '请输入资产编号', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: ['blur', 'change'] }
],
ip: [
{ required: true, message: '请输入IP地址', trigger: ['blur', 'change'] },
{ pattern: /^(\d{1,3}\.){3}\d{1,3}$/, message: 'IP地址格式不正确', trigger: ['blur', 'change'] }
],
port: [
{ required: true, message: '请输入端口号', trigger: ['blur', 'change'] },
{ pattern: /^\d+$/, message: '端口号必须为数字', trigger: ['blur', 'change'] }
],
serviceName: [
{ required: true, message: '请输入服务名称', trigger: ['blur', 'change'] },
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: ['blur', 'change'] }
]
}
//
const loading = ref(false)
//
const dialogVisible = ref(false)
// modelValue
watch(
() => props.modelValue,
(val) => {
dialogVisible.value = val
}
)
//
watch(
() => dialogVisible.value,
(val) => {
emit('update:modelValue', val)
if (!val) {
resetForm()
}
}
)
//
const resetForm = () => {
formRef.value?.resetFields()
Object.assign(formData, {
deviceNo: '',
assetNo: '',
ip: '',
port: '',
serviceName: '',
enabled: true
})
}
//
const setFormData = (data: RelayGetOutput) => {
Object.assign(formData, data)
}
//
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
const api = new UspRelayApi()
let res
if (formData.id) {
//
const updateData: RelayUpdateInput = {
id: formData.id,
...formData
}
res = await api.update(updateData)
} else {
//
const addData: RelayAddInput = {
...formData
}
res = await api.add(addData)
}
if (res.success) {
ElMessage.success(formData.id ? '更新成功' : '新增成功')
dialogVisible.value = false
emit('refresh')
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
})
}
//
const handleClose = () => {
resetForm()
}
//
const open = async (row: any = {}) => {
if (row.id > 0) {
const res = await new UspRelayApi().get({ id: row.id })
if (res?.success) {
Object.assign(formData, res.data)
}
} else {
resetForm()
}
dialogVisible.value = true
}
//
defineExpose({
open,
setFormData
})
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<MyLayout>
<el-card class="my-query-box mt8" shadow="never">
<el-form :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="关键字" prop="keyWord">
<el-input v-model="queryParams.keyWord" placeholder="请输入关键字" clearable />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="queryParams.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="queryParams.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:usp-relay:add'" type="primary" icon="ele-Plus" @click="handleAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
:data="tableData"
style="width: 100%"
v-loading="loading"
border
>
<el-table-column prop="deviceNo" label="设备编号" min-width="100" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="100" show-overflow-tooltip />
<el-table-column prop="ip" label="IP地址" min-width="120" show-overflow-tooltip />
<el-table-column prop="port" label="端口号" min-width="80" show-overflow-tooltip />
<el-table-column prop="serviceName" label="服务名称" min-width="120" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.enabled">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:usp-relay:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="handleEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:usp-relay:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="handleDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="pageInput.currentPage"
v-model:page-size="pageInput.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<relay-form
v-model="showDialog"
:title="formTitle"
ref="formRef"
@refresh="getList"
/>
</MyLayout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UspRelayApi } from '/@/api/admin/UspRelayApi'
import type { RelayGetPageOutput, PageInputRelayGetPageInput } from '/@/api/admin/data-contracts'
import { auth } from '/@/utils/authFunction'
import RelayForm from './components/relay-form.vue'
//
const queryParams = reactive({
keyWord: '',
stDate: '',
edDate: '',
})
//
const pageInput = reactive<PageInputRelayGetPageInput>({
currentPage: 1,
pageSize: 20,
filter: {
keyWord: '',
stDate: '',
edDate: '',
},
})
//
const tableData = ref<RelayGetPageOutput[]>([])
const total = ref(0)
const loading = ref(false)
//
const formRef = ref()
const formTitle = ref('')
const showDialog = ref(false)
//
const queryFormRef = ref()
//
const getList = async () => {
loading.value = true
try {
const res = await new UspRelayApi().getPage(pageInput)
if (res.success) {
tableData.value = res.data?.list || []
total.value = res.data?.total || 0
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
//
const handleQuery = () => {
pageInput.currentPage = 1
pageInput.filter = {
keyWord: queryParams.keyWord,
stDate: queryParams.stDate,
edDate: queryParams.edDate,
}
getList()
}
//
const resetQuery = () => {
queryFormRef.value?.resetFields()
queryParams.stDate = ''
queryParams.edDate = ''
handleQuery()
}
//
const handleAdd = () => {
formTitle.value = '新增继电器'
formRef.value?.open()
}
//
const handleEdit = (row: RelayGetPageOutput) => {
formTitle.value = '编辑继电器'
formRef.value?.open(row)
}
//
const handleDelete = (row: RelayGetPageOutput) => {
ElMessageBox.confirm('确认要删除该继电器吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
const res = await new UspRelayApi().softDelete({ id: row.id })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error(error)
}
})
.catch(() => {})
}
//
const handleSizeChange = (val: number) => {
pageInput.currentPage = 1
pageInput.pageSize = val
getList()
}
//
const handleCurrentChange = (val: number) => {
pageInput.currentPage = val
getList()
}
//
onMounted(() => {
getList()
})
</script>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
<template>
<el-dialog
v-model="visible"
:title="title"
width="500px"
:before-close="handleClose"
destroy-on-close
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="反应器" prop="reactorId">
<el-select
v-model="form.reactorId"
placeholder="请选择反应器"
style="width: 100%"
filterable
>
<el-option
v-for="item in reactorOptions"
:key="item.id"
:label="`${item.name} - ${item.spec}`"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleConfirm" :loading="loading">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
interface ReactorOption {
id: number
name: string
spec: string
}
interface FormData {
reactorId: number | null
remark: string
}
interface Props {
title?: string
reactorOptions?: ReactorOption[]
}
const props = withDefaults(defineProps<Props>(), {
title: '选择反应器',
reactorOptions: () => []
})
const emit = defineEmits<{
confirm: [data: FormData]
}>()
const visible = ref(false)
const loading = ref(false)
const formRef = ref<FormInstance>()
const form = reactive<FormData>({
reactorId: null,
remark: ''
})
const rules: FormRules = {
reactorId: [
{ required: true, message: '请选择反应器', trigger: 'change' }
]
}
const open = () => {
visible.value = true
//
nextTick(() => {
form.reactorId = null
form.remark = ''
formRef.value?.clearValidate()
})
}
const handleClose = () => {
visible.value = false
loading.value = false
}
const handleConfirm = async () => {
try {
await formRef.value?.validate()
loading.value = true
console.log('弹窗确认, 发送的数据:', {
reactorId: form.reactorId,
remark: form.remark
})
emit('confirm', {
reactorId: form.reactorId,
remark: form.remark
})
} catch (error) {
console.error('表单验证失败:', error)
}
}
const closeDialog = () => {
loading.value = false
visible.value = false
}
defineExpose({
open,
closeDialog
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
</style>

View File

@ -0,0 +1,340 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="模板名称">
<el-input v-model="state.filter.keyWord" placeholder="请输入模板名称" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="反应器">
<el-select v-model="state.filter.equReactorId" placeholder="请选择反应器" clearable style="width: 150px">
<el-option
v-for="item in state.reactorOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="date"
placeholder="开始日期"
value-format="YYYY-MM-DD"
style="width: 150px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="date"
placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 150px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery">查询</el-button>
<el-button v-auth="'api:admin:protocol:add'" type="primary" icon="ele-Plus" @click="onAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-if="state.showProtocolList"
:data="state.protocolListData"
style="width: 100%"
v-loading="state.loading"
row-key="id"
border
>
<el-table-column prop="protocolName" label="模板名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="equReactorId" label="反应器" width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ getReactorDescription(row.equReactorId) }}
</template>
</el-table-column>
<el-table-column prop="equReactorId" label="规格" width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ getReactorSpec(row.equReactorId) }}
</template>
</el-table-column>
<el-table-column prop="days" label="培养天数" min-width="100" show-overflow-tooltip />
<el-table-column prop="feedingTaskCount" label="补料任务" min-width="100" show-overflow-tooltip />
<el-table-column label="状态" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.enabled">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdUserName" label="创建者" min-width="100" show-overflow-tooltip />
<el-table-column prop="createdTime" label="创建时间" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="240" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:protocol:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row.id)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:protocol:copy')"
icon="ele-CopyDocument"
size="small"
text
type="success"
@click="onCopy(row)"
>复制</el-button
>
<el-button
v-if="auth('api:admin:protocol:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<protocol-form ref="protocolFormRef" :title="state.protocolFormTitle" @ok="Query" />
<!-- 反应器选择弹窗 -->
<reactor-select-dialog
ref="reactorSelectDialogRef"
:title="state.reactorDialogTitle"
:reactor-options="state.reactorOptions"
@confirm="handleReactorSelect"
/>
</MyLayout>
</template>
<script lang="ts" setup name="admin/protocol">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { ProtocolApi } from '/@/api/admin/ProtocolApi'
import type { ProtocolDto, ProtocolPageInput, ProtocolPageDto } from '/@/api/types/protocolType'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
import { ReactorApi } from '/@/api/admin/ReactorApi'
//
const ProtocolForm = defineAsyncComponent(() => import('./components/protocol-form.vue'))
const ReactorSelectDialog = defineAsyncComponent(() => import('./components/reactor-select-dialog.vue'))
const { proxy } = getCurrentInstance() as any
const protocolFormRef = ref()
const reactorSelectDialogRef = ref()
const state = reactive({
loading: false,
protocolFormTitle: '',
reactorDialogTitle: '',
copyId: null as number | null,
currentOperation: null as { type: 'copy' | 'delete', row: ProtocolDto } | null,
filter: {
keyWord: '',
stDate: '',
edDate: '',
equReactorId: null as number | null
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as ProtocolPageInput,
protocolListData: [] as Array<ProtocolPageDto>,
showQuery: true,
showProtocolList: true,
reactorOptions: [] as Array<{ id: number; name: string; spec: string }>,
})
onMounted(() => {
Query()
getReactorOptions()
})
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
const res = await new ProtocolApi().getPage(state.pageInput).catch(() => {
state.loading = false
})
state.protocolListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
state.loading = false
}
const onAdd = () => {
state.protocolFormTitle = '新增方案模板'
protocolFormRef.value?.open()
}
const onEdit = (row: ProtocolDto) => {
state.protocolFormTitle = '编辑方案模板'
protocolFormRef.value?.open(row)
}
const onCopy = (row: ProtocolDto) => {
state.reactorDialogTitle = '复制方案模板 - 选择目标反应器'
state.currentOperation = { type: 'copy', row }
state.copyId = row.id
reactorSelectDialogRef.value?.open()
}
const onDelete = (row: ProtocolDto) => {
proxy.$modal.confirm('确认要删除该记录吗?').then(async () => {
try {
const res = await new ProtocolApi().softDelete({ id: row.id! })
if (res.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
const onSizeChange = (val: number) => {
state.pageInput.pageSize = val
Query()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
Query()
}
const getReactorOptions = async () => {
try {
const res = await new ReactorApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.reactorOptions = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.productID ?? '',
spec: item.specification ?? ''
})) ?? []
}
} catch (error) {
console.error('获取反应器列表失败:', error)
}
}
const getReactorDescription = (reactorId: number) => {
const reactor = state.reactorOptions.find(item => item.id == reactorId)
return reactor?.name ?? ''
}
const getReactorSpec = (reactorId: number) => {
const reactor = state.reactorOptions.find(item => item.id == reactorId)
return reactor?.spec ?? ''
}
const onReset = () => {
state.filter = {
keyWord: '',
stDate: '',
edDate: '',
equReactorId: null
}
onQuery()
}
//
const handleReactorSelect = async (data: { reactorId: number | null, remark: string }) => {
if (!state.currentOperation) return
try {
const { type, row } = state.currentOperation
if (type === 'copy' && data.reactorId) {
const res = await new ProtocolApi().copy({
id: state.copyId!,
reactorId: data.reactorId
})
if (res.success) {
proxy.$modal.msgSuccess('复制成功')
Query()
}
}
//
reactorSelectDialogRef.value?.closeDialog()
state.currentOperation = null
} catch (error) {
console.error('操作失败:', error)
reactorSelectDialogRef.value?.closeDialog()
}
}
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-flex {
display: flex;
}
.my-flex-between {
justify-content: space-between;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,370 @@
<template>
<div class="testequ-form">
<el-dialog
v-model="state.isShowDialog"
destroy-on-close
:title="props.title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备类型" prop="equipmentType" :rules="[{ required: true, message: '请选择设备类型', trigger: ['change'] }]">
<el-select v-model="form.equipmentType" placeholder="请选择设备类型" style="width: 100%">
<el-option
v-for="type in state.equipmentTypeOptions"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="equipmentNo" :rules="[{ required: true, message: '请输入设备编号', trigger: ['blur', 'change'] }]">
<el-input v-model="form.equipmentNo" clearable placeholder="请输入设备编号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo" :rules="[{ required: true, message: '请输入资产编号', trigger: ['blur', 'change'] }]">
<el-input v-model="form.assetNo" clearable placeholder="请输入资产编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备型号" prop="equipmenModel" :rules="[{ required: true, message: '请选择设备型号', trigger: ['change'] }]">
<el-select
v-model="form.equipmenModel"
placeholder="请选择设备型号"
filterable
clearable
:loading="state.dictLoading"
style="width: 100%"
>
<el-option
v-for="model in state.equipmentModelOptions"
:key="model.id"
:label="model.name"
:value="model.code"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="负责人" prop="principalId" :rules="[{ required: true, message: '请选择负责人', trigger: ['change'] }]">
<el-select
v-model="form.principalId"
placeholder="请选择设备负责人"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="user in state.userOptions"
:key="user.id"
:label="`${user.name} (${user.userName})`"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="描述" prop="description">
<el-input
v-model="form.description"
clearable
type="textarea"
:rows="3"
placeholder="请输入设备描述"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 配置信息 -->
<div class="form-section">
<div class="section-title">配置信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="序列号" prop="serialNumber" :rules="[{ required: true, message: '请输入序列号', trigger: ['blur', 'change'] }]">
<el-input v-model="form.serialNumber" clearable placeholder="请输入序列号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="私钥" prop="privateKey" :rules="[{ required: true, message: '请输入私钥', trigger: ['blur', 'change'] }]">
<el-input v-model="form.privateKey" type="password" placeholder="请输入私钥" show-password />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.loading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="TestequForm">
import { reactive, ref, getCurrentInstance, onMounted, toRefs } from 'vue'
import { TestequApi } from '/@/api/admin/TestequApi'
import { UserApi } from '/@/api/admin/User'
import { DictApi } from '/@/api/admin/Dict'
import { UserGetPageOutput, PageInputUserGetPageInput, TestequAddInput, TestequUpdateInput, DictGetListOutput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
//
const props = defineProps({
title: String,
})
// /
const emit = defineEmits(['refresh'])
//
const formRef = ref()
const { proxy } = getCurrentInstance() as any
//
type TestequFormData = {
id?: number
equipmentType: number | undefined
equipmentNo: string
serialNumber: string
privateKey: string
assetNo: string
equipmenModel: string
principalId: number | undefined
description: string
}
const state = reactive({
isShowDialog: false,
loading: false,
dictLoading: false,
form: {
id: undefined,
equipmentType: undefined,
equipmentNo: '',
serialNumber: '',
privateKey: '',
assetNo: '',
equipmenModel: '',
principalId: undefined,
description: '',
} as TestequFormData,
userOptions: [] as Array<UserGetPageOutput>,
equipmentModelOptions: [] as Array<DictGetListOutput>,
equipmentTypeOptions: [
{ label: 'BGA', value: 1 },
{ label: 'Vicell', value: 3 },
{ label: 'OSMO', value: 5 },
{ label: 'Cedex', value: 7 }
],
})
const { form } = toRefs(state)
onMounted(() => {
getUserOptions()
getEquipmentModelOptions()
})
//
const openDialog = async (row?: any) => {
resetForm()
//
if (state.equipmentModelOptions.length === 0) {
state.dictLoading = true
await getEquipmentModelOptions()
state.dictLoading = false
}
//
if (state.userOptions.length === 0) {
await getUserOptions()
}
if (row && row.id) {
//
try {
const res = await new TestequApi().get({ id: row.id })
if (res?.success && res.data) {
state.form = {
id: res.data.id,
equipmentType: res.data.equipmentType,
equipmentNo: res.data.equipmentNo || '',
serialNumber: res.data.serialNumber || '',
privateKey: res.data.privateKey || '',
assetNo: res.data.assetNo || '',
equipmenModel: res.data.equipmenModel || '',
principalId: res.data.principalId,
description: res.data.description || '',
}
}
} catch (error: any) {
//
if (error.response?.status === 500) {
proxy.$modal.msgError('服务器内部错误,请检查后台服务')
} else {
proxy.$modal.msgError(`获取详情失败: ${error.response?.data?.msg || error.message}`)
}
return
}
}
state.isShowDialog = true
}
//
const closeDialog = () => {
state.isShowDialog = false
resetForm()
}
//
const resetForm = () => {
state.form = {
id: undefined,
equipmentType: undefined,
equipmentNo: '',
serialNumber: '',
privateKey: '',
assetNo: '',
equipmenModel: '',
principalId: undefined,
description: '',
}
}
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000, //
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getEquipmentModelOptions = async () => {
try {
const res = await new DictApi().getList(['TESTINGEQU'])
if (res?.success && res.data) {
// APIkey testingequ
state.equipmentModelOptions = res.data['testingequ'] || []
}
} catch (error) {
//
}
}
//
const onCancel = () => {
closeDialog()
}
//
const onSubmit = () => {
formRef.value.validate(async (valid: boolean) => {
if (!valid) return false
state.loading = true
try {
let res = {} as any
if (state.form.id) {
res = await new TestequApi().update(state.form as TestequUpdateInput, { showSuccessMessage: true })
} else {
res = await new TestequApi().add(state.form as TestequAddInput, { showSuccessMessage: true })
}
if (res?.success) {
closeDialog()
emit('refresh')
eventBus.emit('refreshTestequ' as keyof MittType<any>)
}
} catch (error: any) {
//
proxy.$modal.msgError('操作失败')
} finally {
state.loading = false
}
})
}
//
defineExpose({
openDialog,
closeDialog,
})
</script>
<style scoped lang="scss">
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,339 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="设备编号、序列号、资产编号" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="设备类型">
<el-select v-model="state.filter.equipmentType" placeholder="请选择设备类型" clearable style="width: 150px">
<el-option
v-for="type in state.equipmentTypeOptions"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button v-if="auth('api:admin:testing-equipment:get-page')" @click="onQuery" type="primary">
<SvgIcon name="ele-Search" />查询
</el-button>
<el-button v-auth="'api:admin:testing-equipment:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-if="state.showTestequList"
:data="state.testequListData"
style="width: 100%"
v-loading="state.loading"
row-key="id"
default-expand-all
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="equipmentType" label="设备类型" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ getEquipmentTypeName(row.equipmentType) }}
</template>
</el-table-column>
<el-table-column prop="equipmentNo" label="设备编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="120" show-overflow-tooltip />
<el-table-column label="设备型号" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getEquipmentModelName(row.equipmenModel) }}
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
<el-table-column prop="principalId" label="负责人" width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ getUserName(row.principalId) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:testing-equipment:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:testing-equipment:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<testequ-form ref="testequFormRef" :title="state.testequFormTitle"></testequ-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/testequ">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { TestequApi } from '/@/api/admin/TestequApi'
import { UserApi } from '/@/api/admin/User'
import { DictApi } from '/@/api/admin/Dict'
import { UserGetPageOutput, PageInputUserGetPageInput, TestequGetPageOutput, PageInputTestequGetPageInput, DictGetListOutput } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
//
const TestequForm = defineAsyncComponent(() => import('./components/testequ-form.vue'))
const { proxy } = getCurrentInstance() as any
const testequFormRef = ref()
const state = reactive({
loading: false,
testequFormTitle: '',
filter: {
keyWord: '',
equipmentType: undefined as number | undefined,
stDate: '',
edDate: '',
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputTestequGetPageInput,
testequListData: [] as Array<TestequGetPageOutput>,
userOptions: [] as Array<UserGetPageOutput>,
equipmentModelOptions: [] as Array<DictGetListOutput>,
equipmentTypeOptions: [
{ label: 'BGA', value: 1 },
{ label: 'Vicell', value: 3 },
{ label: 'OSMO', value: 5 },
{ label: 'Cedex', value: 7 }
],
showQuery: true,
showTestequList: true,
})
onMounted(() => {
getUserOptions()
getEquipmentModelOptions()
Query()
eventBus.off('refreshTestequ' as keyof MittType<any>)
eventBus.on('refreshTestequ' as keyof MittType<any>, () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshTestequ' as keyof MittType<any>)
})
const onChangeTestequList = () => {
state.showTestequList = !state.showTestequList
if (state.showTestequList) {
Query()
}
}
//
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000, //
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
//
}
}
//
const getEquipmentModelOptions = async () => {
try {
const res = await new DictApi().getList(['TESTINGEQU'])
if (res?.success && res.data) {
// APIkey testingequ
state.equipmentModelOptions = res.data['testingequ'] || []
}
} catch (error) {
//
}
}
//
const getEquipmentTypeName = (type: number) => {
const option = state.equipmentTypeOptions.find(item => item.value === type)
return option ? option.label : type?.toString() || ''
}
//
const getUserName = (userId?: number) => {
if (!userId) return ''
const user = state.userOptions.find(item => item.id === userId)
return user ? `${user.name} (${user.userName})` : `未知用户(${userId})`
}
//
const getEquipmentModelName = (code?: string) => {
if (!code) return ''
const model = state.equipmentModelOptions.find(item => item.code === code)
return model ? model.name : code
}
//
const Query = async () => {
state.loading = true
try {
// filter
state.pageInput.filter = {
keyWord: state.filter.keyWord || '',
equipmentType: state.filter.equipmentType ? Number(state.filter.equipmentType) : undefined,
stDate: state.filter.stDate || null,
edDate: state.filter.edDate || null,
}
const res = await new TestequApi().getPage(state.pageInput)
if (res?.success && res.data) {
state.testequListData = res.data.list || []
state.total = res.data.total || 0
} else {
proxy.$modal.msgError(res?.msg || '查询失败')
}
} catch (error: any) {
//
if (error.response) {
if (error.response.status === 500) {
proxy.$modal.msgError('服务器内部错误,请检查后台服务')
} else if (error.response.status === 401) {
proxy.$modal.msgError('认证失败,请重新登录')
} else if (error.response.status === 403) {
proxy.$modal.msgError('权限不足')
} else if (error.response.status === 405) {
proxy.$modal.msgError('API方法不被允许请检查后端路由配置')
} else {
proxy.$modal.msgError(`请求失败: ${error.response.status} ${error.response.statusText}`)
}
} else if (error.request) {
proxy.$modal.msgError('网络连接失败,请检查网络状态')
} else {
proxy.$modal.msgError(`查询失败: ${error.message}`)
}
} finally {
state.loading = false
}
}
const onQuery = () => {
state.pageInput.currentPage = 1
Query()
}
const onAdd = () => {
state.testequFormTitle = '新增检测设备'
testequFormRef.value.openDialog()
}
const onEdit = (row: TestequGetPageOutput) => {
state.testequFormTitle = '编辑检测设备'
testequFormRef.value.openDialog(row)
}
const onDelete = (row: TestequGetPageOutput) => {
// rowid
console.log('删除操作 - 传入的row对象:', row)
console.log('删除操作 - ID值:', row.id, '类型:', typeof row.id)
proxy.$modal
.confirmDelete(`确定要删除设备【${row.equipmentNo || row.assetNo || '未知设备'}】?`)
.then(async () => {
try {
console.log('发送删除请求 - ID参数:', { id: row.id! })
const res = await new TestequApi().softDelete({ id: row.id! }, { loading: true })
console.log('删除API响应:', res)
if (res?.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error: any) {
console.error('删除API错误:', error)
//
if (error.response?.status === 500) {
proxy.$modal.msgError('服务器内部错误,请检查后台服务')
} else {
proxy.$modal.msgError(`删除失败: ${error.response?.data?.msg || error.message}`)
}
}
})
.catch(() => {})
}
const onSizeChange = (val: number) => {
state.pageInput.pageSize = val
Query()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
Query()
}
</script>

View File

@ -113,11 +113,12 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="职位"> <el-form-item label="团队">
<el-input v-model="form.staff.position" autocomplete="off" /> <el-select v-model="form.team" placeholder="请选择团队" class="w100">
<el-option v-for="item in state.teamList" :key="item.label" :label="item.label" :value="item.value" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="性别"> <el-form-item label="性别">
<el-select v-model="form.staff.sex" placeholder="请选择性别" class="w100"> <el-select v-model="form.staff.sex" placeholder="请选择性别" class="w100">
@ -158,7 +159,7 @@ import { validatorPwd } from '/@/utils/validators'
import eventBus from '/@/utils/mitt' import eventBus from '/@/utils/mitt'
import { FormInstance } from 'element-plus' import { FormInstance } from 'element-plus'
import { verifyCnAndSpace } from '/@/utils/toolsValidate' import { verifyCnAndSpace } from '/@/utils/toolsValidate'
import { Sex } from '/@/api/admin/enum-contracts' import { Sex, Team } from '/@/api/admin/enum-contracts'
import { toOptionsByValue } from '/@/utils/enum' import { toOptionsByValue } from '/@/utils/enum'
// //
@ -186,6 +187,7 @@ const state = reactive({
orgTreeData: [] as OrgGetListOutput[], orgTreeData: [] as OrgGetListOutput[],
roleTreeData: [] as RoleGetListOutput[], roleTreeData: [] as RoleGetListOutput[],
sexList: toOptionsByValue(Sex), sexList: toOptionsByValue(Sex),
teamList: toOptionsByValue(Team),
}) })
const { form } = toRefs(state) const { form } = toRefs(state)

View File

@ -64,8 +64,10 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="职位"> <el-form-item label="团队">
<el-input v-model="form.staff.position" autocomplete="off" /> <el-select v-model="form.team" placeholder="请选择团队" class="w100">
<el-option v-for="item in state.teamList" :key="item.label" :label="item.label" :value="item.value" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
@ -104,7 +106,7 @@ import { listToTree } from '/@/utils/tree'
import { isMobile, testMobile, testEmail } from '/@/utils/test' import { isMobile, testMobile, testEmail } from '/@/utils/test'
import eventBus from '/@/utils/mitt' import eventBus from '/@/utils/mitt'
import { FormInstance } from 'element-plus' import { FormInstance } from 'element-plus'
import { Sex } from '/@/api/admin/enum-contracts' import { Sex, Team } from '/@/api/admin/enum-contracts'
import { toOptionsByValue } from '/@/utils/enum' import { toOptionsByValue } from '/@/utils/enum'
// //
@ -125,9 +127,11 @@ const state = reactive({
sureLoading: false, sureLoading: false,
form: { form: {
roleIds: [] as any, roleIds: [] as any,
team: 5,
} as UserAddInput & UserUpdateInput, } as UserAddInput & UserUpdateInput,
roleTreeData: [] as RoleGetListOutput[], roleTreeData: [] as RoleGetListOutput[],
sexList: toOptionsByValue(Sex), sexList: toOptionsByValue(Sex),
teamList: toOptionsByValue(Team),
}) })
const { form } = toRefs(state) const { form } = toRefs(state)

View File

@ -0,0 +1,200 @@
<template>
<el-dialog
v-model="visible"
:title="title"
width="600px"
:before-close="handleClose"
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
@keyup.enter="onSubmit"
>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="USP批次名称" prop="uspBatchName">
<el-input
v-model="form.uspBatchName"
placeholder="请输入USP批次名称"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="USP批次编号" prop="uspBatchNo">
<el-input
v-model="form.uspBatchNo"
placeholder="请输入USP批次编号"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="项目" prop="projectId">
<el-select
v-model="form.projectId"
placeholder="请选择项目"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="loading">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { UspBatchApi } from '/@/api/admin/UspBatchApi'
import { ProjectApi } from '/@/api/admin/ProjectApi'
import type { UspBatchDto } from '/@/api/types/uspBatchType'
interface Props {
title?: string
}
const props = withDefaults(defineProps<Props>(), {
title: 'USP批次信息'
})
const emit = defineEmits<{
ok: []
}>()
const visible = ref(false)
const loading = ref(false)
const formRef = ref<FormInstance>()
const isEdit = ref(false)
const form = reactive<UspBatchDto>({
id: undefined,
uspBatchName: '',
uspBatchNo: '',
projectId: null,
isDown: true,
titer: null
})
const projectOptions = ref<Array<{ id: number; name: string }>>([])
const rules: FormRules = {
uspBatchName: [
{ required: true, message: '请输入USP批次名称', trigger: 'blur' },
{ max: 255, message: 'USP批次名称长度不能超过255个字符', trigger: 'blur' }
],
uspBatchNo: [
{ required: true, message: '请输入USP批次编号', trigger: 'blur' },
{ max: 50, message: 'USP批次编号长度不能超过50个字符', trigger: 'blur' }
],
projectId: [
{ required: true, message: '请选择项目', trigger: 'change' }
]
}
onMounted(() => {
getProjectOptions()
})
const open = (data?: UspBatchDto) => {
visible.value = true
isEdit.value = !!data?.id
nextTick(() => {
if (data) {
Object.assign(form, data)
} else {
resetForm()
}
formRef.value?.clearValidate()
})
}
const resetForm = () => {
Object.assign(form, {
id: undefined,
uspBatchName: '',
uspBatchNo: '',
projectId: null,
isDown: true,
titer: null
})
}
const handleClose = () => {
visible.value = false
loading.value = false
}
const onSubmit = async () => {
try {
await formRef.value?.validate()
loading.value = true
const api = new UspBatchApi()
const res = isEdit.value
? await api.update(form)
: await api.add(form)
if (res.success) {
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
emit('ok')
handleClose()
}
} catch (error) {
console.error('提交失败:', error)
} finally {
loading.value = false
}
}
const getProjectOptions = async () => {
try {
const res = await new ProjectApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
projectOptions.value = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.projectName ?? ''
})) ?? []
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
defineExpose({
open
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
</style>

View File

@ -0,0 +1,347 @@
<template>
<MyLayout>
<el-card class="my-query-box" shadow="never">
<el-form :model="state.filter" :inline="true" @keyup.enter="onQuery">
<el-form-item label="关键字">
<el-input
v-model="state.filter.keyWord"
placeholder="USP批次名称/编号"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="项目">
<el-select
v-model="state.filter.projectId"
placeholder="请选择项目"
clearable
style="width: 200px"
>
<el-option
v-for="item in state.projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="state.filter.stDate"
type="date"
placeholder="开始日期"
value-format="YYYY-MM-DD"
style="width: 150px"
/>
-
<el-date-picker
v-model="state.filter.edDate"
type="date"
placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 150px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery">查询</el-button>
<el-button v-auth="'api:admin:pre-batch:add'" type="primary" icon="ele-Plus" @click="onAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table
:data="state.uspBatchListData"
style="width: 100%"
v-loading="state.loading"
row-key="id"
border
>
<el-table-column prop="preBatchName" label="批次名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="preBatchNo" label="批次编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="projectId" label="项目" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getProjectName(row.projectId) }}
</template>
</el-table-column>
<el-table-column prop="projectUserName" label="项目负责人" min-width="100" show-overflow-tooltip />
<el-table-column prop="titer" label="titer" min-width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ row.titer || '-' }}
</template>
</el-table-column>
<el-table-column prop="createdUserRealName" label="创建者" min-width="100" show-overflow-tooltip />
<el-table-column prop="createdTime" label="创建时间" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<div class="action-buttons">
<el-button
v-if="auth('api:admin:pre-batch:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:pre-batch:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</div>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<UspbatchForm ref="uspBatchFormRef" :title="state.formTitle" @ok="Query" />
</MyLayout>
</template>
<script lang="ts" setup name="admin/uspBatch">
import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent } from 'vue'
import { UspBatchApi } from '/@/api/admin/UspBatchApi'
import { ProjectApi } from '/@/api/admin/ProjectApi'
import type { UspBatchDto, UspBatchPageInput, UspBatchPageDto } from '/@/api/types/uspBatchType'
import { auth } from '/@/utils/authFunction'
//
const UspbatchForm = defineAsyncComponent(() => import('./components/uspbatch-form.vue'))
const { proxy } = getCurrentInstance() as any
const uspBatchFormRef = ref()
const state = reactive({
loading: false,
formTitle: '',
filter: {
keyWord: '',
projectId: null as number | null,
isDown: true,
stDate: '',
edDate: ''
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as UspBatchPageInput,
uspBatchListData: [] as Array<UspBatchPageDto>,
projectOptions: [] as Array<{ id: number; name: string }>,
})
onMounted(() => {
Query()
getProjectOptions()
})
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
try {
const res = await new UspBatchApi().getPage(state.pageInput)
state.uspBatchListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
} catch (error) {
console.error('查询失败:', error)
} finally {
state.loading = false
}
}
const onAdd = () => {
state.formTitle = '新增USP批次'
uspBatchFormRef.value?.open()
}
const onEdit = (row: UspBatchDto) => {
state.formTitle = '编辑USP批次'
uspBatchFormRef.value?.open(row)
}
const onDelete = (row: UspBatchDto) => {
proxy.$modal.confirm('确认要删除该记录吗?').then(async () => {
try {
const res = await new UspBatchApi().softDelete({ id: row.id! })
if (res.success) {
proxy.$modal.msgSuccess('删除成功')
Query()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
const onSizeChange = (val: number) => {
state.pageInput.pageSize = val
Query()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
Query()
}
const onReset = () => {
state.filter = {
keyWord: '',
projectId: null,
isDown: true,
stDate: '',
edDate: ''
}
Query()
}
const getProjectOptions = async () => {
try {
const res = await new ProjectApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.projectOptions = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.projectName ?? ''
})) ?? []
}
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
const getProjectName = (projectId: number) => {
const project = state.projectOptions.find(item => item.id === projectId)
return project?.name ?? ''
}
</script>
<style lang="scss" scoped>
.my-query-box {
margin-bottom: 8px;
:deep(.el-form-item) {
margin-bottom: 16px;
}
:deep(.el-form--inline .el-form-item) {
margin-right: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
:deep(.el-card__body) {
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
}
.my-flex {
display: flex;
align-items: center;
}
.my-flex-between {
justify-content: space-between;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
//
:deep(.el-table) {
.el-table__header-wrapper {
th {
background-color: #fafafa;
color: #606266;
font-weight: 500;
}
}
.el-table__body-wrapper {
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
}
//
:deep(.el-pagination) {
display: flex;
justify-content: flex-end;
margin-top: 16px;
.el-pager li.is-active {
background-color: var(--el-color-primary);
color: white;
}
}
//
.action-buttons {
display: flex;
gap: 8px;
.el-button {
margin-left: 0;
}
}
//
.status-tag {
&.enabled {
background-color: #f0f9ff;
color: #1890ff;
border: 1px solid #d4edda;
}
&.disabled {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
}
</style>

View File

@ -0,0 +1,155 @@
<template>
<div class="uspreason-form">
<el-dialog
v-model="state.visible"
destroy-on-close
:title="title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="600px"
>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" size="default">
<div class="form-section">
<el-row :gutter="25">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="原因类型" prop="revokeType">
<el-select v-model="form.revokeType" placeholder="请选择原因类型" style="width: 100%">
<el-option
v-for="item in state.revokeTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="原因描述" prop="revokeReason">
<el-input
v-model="form.revokeReason"
type="textarea"
:rows="3"
placeholder="请输入原因描述"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.loading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, defineProps, defineExpose, onMounted } from 'vue'
import { RevokeReasonApi } from '/@/api/admin/revoke-reason'
import type {
RevokeReasonPageInput,
RevokeReasonOutput,
RevokeReasonPageDto,
RevokeTypeEnumItem,
RevokeReasonDto
} from '/@/api/types/RevokeType'
import eventBus from '/@/utils/mitt'
const props = defineProps<{ title: string }>()
const emit = defineEmits(['ok'])
const formRef = ref()
const state = reactive({
visible: false,
loading: false,
revokeTypeOptions: [] as RevokeTypeEnumItem[]
})
const form = reactive<RevokeReasonDto>({
revokeReason: '',
revokeType: '',
isDown: false,
id: 0
})
const rules = {
revokeReason: [{ required: true, message: '请输入原因描述', trigger: 'blur' }],
revokeType: [{ required: true, message: '请选择类型', trigger: 'change' }]
}
//
const getRevokeTypeOptions = async () => {
try {
const res = await new RevokeReasonApi().getRevokeTypeEnumList()
if (res?.success && res.data) {
state.revokeTypeOptions = res.data
}
} catch (error) {
console.error('获取撤回类型枚举列表失败:', error)
}
}
function open(row?: Partial<RevokeReasonDto>) {
if (row) {
Object.assign(form, row)
} else {
Object.assign(form, { revokeReason: '', revokeType: '', isDown: false, id: 0 })
}
state.visible = true
}
function onCancel() {
state.visible = false
}
function onSubmit() {
formRef.value.validate(async (valid: boolean) => {
if (!valid) return
state.loading = true
try {
if (form.id) {
await new RevokeReasonApi().update(form)
} else {
await new RevokeReasonApi().add(form)
}
state.visible = false
eventBus.emit('refreshUspReason')
} finally {
state.loading = false
}
})
}
onMounted(() => {
getRevokeTypeOptions()
})
defineExpose({ open })
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
:deep(.el-textarea__inner) {
resize: none;
}
</style>

View File

@ -0,0 +1,213 @@
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" :label-position="'left'" @submit.stop.prevent>
<el-form-item label="原因描述" prop="revokeReason">
<el-input v-model="state.filter.keyWord" placeholder="请输入原因描述" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="原因类型" prop="revokeType">
<el-select v-model="state.filter.revokeType" placeholder="请选择原因类型" clearable style="width: 200px">
<el-option v-for="item in state.revokeTypeOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker v-model="state.filter.startTime" type="datetime" placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker v-model="state.filter.endTime" type="datetime" placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery">查询</el-button>
<el-button v-auth="'api:admin:revoke-reason:add'" type="primary" icon="ele-Plus"
@click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table v-loading="loading" :data="state.revokeReasonList" row-key="id" style="width: 100%" border>
<el-table-column label="原因类型" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getRevokeTypeName(row.revokeType) }}
</template>
</el-table-column>
<el-table-column prop="revokeReason" label="原因描述" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:revoke-reason:update'" icon="ele-EditPen" size="small" text type="primary"
@click="handleEdit(row)">编辑</el-button>
<el-button v-auth="'api:admin:revoke-reason:soft-delete'" icon="ele-Delete" size="small" text type="danger"
@click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination v-model:current-page="state.pageInput.currentPage" v-model:page-size="state.pageInput.pageSize"
:total="state.total" :page-sizes="[10, 20, 50, 100]" size="small" background @size-change="handleSizeChange"
@current-change="handleCurrentChange" layout="total, sizes, prev, pager, next, jumper" />
</div>
</el-card>
<uspreason-form ref="formRef" :title="state.formTitle" @ok="getList" />
</my-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { RevokeReasonApi } from '/@/api/admin/revoke-reason'
import eventBus from '/@/utils/mitt'
import type {
RevokeReasonPageInput,
RevokeReasonOutput,
RevokeReasonPageDto,
RevokeTypeEnumItem
} from '/@/api/types/RevokeType'
import UspreasonForm from './components/uspreason-form.vue'
const loading = ref(false)
const formRef = ref()
const state = reactive({
loading: false,
formTitle: '',
total: 0,
filter: {
keyWord: "",
revokeType: "",
isDown: false,
startTime: "",
endTime: ""
},
pageInput: {
currentPage: 1,
pageSize: 20,
} as RevokeReasonPageInput,
revokeReasonList: [] as Array<RevokeReasonPageDto>,
revokeTypeOptions: [] as RevokeTypeEnumItem[]
})
/** 查询列表 */
const getList = async () => {
loading.value = true
state.pageInput.filter = state.filter
try {
const res = await new RevokeReasonApi().getPage(state.pageInput)
state.revokeReasonList = res.data?.list || []
state.total = res.data?.total || 0
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
/** 获取撤销类型枚举列表 */
const getRevokeTypeOptions = async () => {
try {
const res = await new RevokeReasonApi().getRevokeTypeEnumList()
state.revokeTypeOptions = res.data ?? []
} catch (error) {
console.error('获取撤销类型枚举列表失败:', error)
}
}
const getRevokeTypeName = (revokeType: string) => {
const item = state.revokeTypeOptions.find(item => item.value === Number(revokeType))
return item?.label || ''
}
/** 搜索按钮操作 */
const handleQuery = () => {
state.pageInput.currentPage = 1
getList()
}
/** 新增按钮操作 */
const handleAdd = () => {
state.formTitle = '新增原因'
formRef.value?.open()
}
/** 修改按钮操作 */
const handleEdit = (row: RevokeReasonOutput) => {
state.formTitle = '编辑原因'
formRef.value?.open(row)
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
ElMessageBox.confirm('确认要删除该记录吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await new RevokeReasonApi().softDelete({ id: id })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
/** 每页条数改变 */
const handleSizeChange = (val: number) => {
state.pageInput.pageSize = val
getList()
}
/** 当前页改变 */
const handleCurrentChange = (val: number) => {
state.pageInput.currentPage = val
getList()
}
onMounted(() => {
getRevokeTypeOptions()
getList()
eventBus.on('refreshUspReason', getList)
})
onBeforeMount(() => {
eventBus.off('refreshUspReason')
})
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-flex {
display: flex;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<div>
<el-dialog
v-model="state.showDialog"
destroy-on-close
title="通讯设置"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="600px"
>
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="子系统IP" prop="subSystemIP">
<el-input v-model="form.subSystemIP" clearable placeholder="请输入子系统IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="子系统端口" prop="subSystemIPPort">
<el-input v-model="form.subSystemIPPort" clearable placeholder="请输入子系统端口" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备IP" prop="deviceIP">
<el-input v-model="form.deviceIP" clearable placeholder="请输入设备IP地址" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备端口" prop="deviceIPPort">
<el-input v-model="form.deviceIPPort" clearable placeholder="请输入设备端口" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="服务名称" prop="serviceName">
<el-input v-model="form.serviceName" clearable placeholder="请输入服务名称" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSure" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="admin/Uspscale/communication-form">
import { reactive, toRefs, ref } from 'vue'
interface CommunicationData {
subSystemIP?: string | null
subSystemIPPort?: string | null
deviceIP?: string | null
deviceIPPort?: string | null
serviceName?: string | null
}
const formRef = ref()
const state = reactive({
showDialog: false,
form: {
subSystemIP: '',
subSystemIPPort: '',
deviceIP: '',
deviceIPPort: '',
serviceName: '',
} as CommunicationData,
})
const { form } = toRefs(state)
const emit = defineEmits(['confirm'])
//
const open = (data: CommunicationData = {}) => {
state.form = {
subSystemIP: data.subSystemIP || '',
subSystemIPPort: data.subSystemIPPort || '',
deviceIP: data.deviceIP || '',
deviceIPPort: data.deviceIPPort || '',
serviceName: data.serviceName || '',
}
state.showDialog = true
}
//
const onCancel = () => {
state.showDialog = false
}
//
const onSure = () => {
emit('confirm', { ...state.form })
state.showDialog = false
}
defineExpose({
open,
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,364 @@
<template>
<div>
<el-dialog
v-model="state.showDialog"
destroy-on-close
:title="title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备编号" prop="deviceNo" :rules="[{ required: true, message: '请输入设备编号', trigger: ['blur', 'change'] }]">
<el-input v-model="form.deviceNo" clearable placeholder="请输入设备编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="资产编号" prop="assetNo">
<el-input v-model="form.assetNo" clearable placeholder="请输入资产编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="型号" prop="model">
<el-select v-model="form.model" placeholder="请选择设备型号" clearable style="width: 100%">
<el-option
v-for="model in state.modelOptions"
:key="model.value"
:label="model.label"
:value="model.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="规格" prop="specification">
<el-input v-model="form.specification" clearable placeholder="请输入设备规格" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="房间" prop="roomID" :rules="[{ required: true, message: '请选择房间', trigger: ['blur', 'change'] }]">
<el-select v-model="form.roomID" placeholder="请选择设备所在房间" style="width: 100%">
<el-option
v-for="room in state.roomOptions"
:key="room.id"
:label="room.roomName"
:value="room.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="设备负责人" prop="principalId">
<el-select
v-model="form.principalId"
placeholder="请输入姓名搜索设备负责人"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="user in state.userOptions"
:key="user.id"
:label="`${user.name} (${user.userName})`"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 技术参数 -->
<div class="form-section">
<div class="section-title">技术参数</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="精度(g)" prop="scalePrecision">
<el-input-number v-model="form.scalePrecision" :min="0" :precision="1" style="width: 100%" placeholder="请输入设备精度" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="报警下限值(g)" prop="warningLowerLimit">
<el-input-number v-model="form.warningLowerLimit" :min="0" :precision="2" style="width: 100%" placeholder="请输入报警下限值" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 设备配置 -->
<div class="form-section">
<div class="section-title">设备配置</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="维护标记">
<el-switch
v-model="form.maintenanceFlag"
active-text="维护中"
inactive-text="正常"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="是否补料地秤">
<el-switch
v-model="form.isFeedingScale"
active-text="是"
inactive-text="否"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="启用状态">
<el-switch
v-model="form.enabled"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="通讯设置">
<el-button type="primary" @click="openCommunicationForm" icon="ele-Setting">
配置通讯参数
</el-button>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSure" size="default" :loading="state.sureLoading"> </el-button>
</span>
</template>
</el-dialog>
<!-- 通讯设置表单 -->
<communication-form ref="communicationFormRef" @confirm="onCommunicationConfirm" />
</div>
</template>
<script lang="ts" setup name="admin/Uspscale/form">
import { reactive, toRefs, ref, getCurrentInstance, onMounted, defineAsyncComponent } from 'vue'
import { UspscaleUpdateInput, RoomGetPageOutput, PageInputRoomGetPageInput, UserGetPageOutput, PageInputUserGetPageInput } from '/@/api/admin/data-contracts'
import { UspscaleApi } from '/@/api/admin/UspscaleApi'
import { RoomApi } from '/@/api/admin/Room'
import { UserApi } from '/@/api/admin/User'
import eventBus from '/@/utils/mitt'
//
const CommunicationForm = defineAsyncComponent(() => import('./communication-form.vue'))
const { proxy } = getCurrentInstance() as any
defineProps({
title: {
type: String,
default: '',
},
})
const formRef = ref()
const communicationFormRef = ref()
const state = reactive({
showDialog: false,
sureLoading: false,
form: {
enabled: true,
maintenanceFlag: false,
isFeedingScale: false,
roomID: 0,
} as UspscaleUpdateInput,
roomOptions: [] as Array<RoomGetPageOutput>,
userOptions: [] as Array<UserGetPageOutput>,
modelOptions: [
{ label: 'CAIS2-U 1500kg', value: '001' },
{ label: 'METTLER', value: '3' },
{ label: 'OHAUS', value: '5' }
],
data: [],
})
const { form } = toRefs(state)
onMounted(() => {
getRoomOptions()
getUserOptions()
})
const getRoomOptions = async () => {
try {
const roomPageInput = {
currentPage: 1,
pageSize: 1000, //
filter: {
keyWord: '',
}
} as PageInputRoomGetPageInput
const res = await new RoomApi().getPage(roomPageInput)
if (res?.success) {
state.roomOptions = res.data?.list ?? []
}
} catch (error) {
console.error('获取房间列表失败:', error)
}
}
const getUserOptions = async () => {
try {
const userPageInput = {
currentPage: 1,
pageSize: 1000, //
filter: {
orgId: null,
}
} as PageInputUserGetPageInput
const res = await new UserApi().getPage(userPageInput)
if (res?.success) {
state.userOptions = res.data?.list ?? []
}
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
//
const open = async (row: any = {}) => {
proxy.$modal.loading()
if (row.id > 0) {
const res = await new UspscaleApi().get({ id: row.id }, { loading: true })
if (res?.success) {
let formData = res.data as UspscaleUpdateInput
state.form = formData
}
} else {
state.form = {
enabled: true,
maintenanceFlag: false,
isFeedingScale: false,
roomID: undefined,
principalId: undefined,
warningLowerLimit: 0,
scalePrecision: 0,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
subSystemIP: '',
subSystemIPPort: '',
deviceIP: '',
deviceIPPort: '',
serviceName: '',
} as UspscaleUpdateInput
}
proxy.$modal.closeLoading()
state.showDialog = true
}
//
const openCommunicationForm = () => {
const communicationData = {
subSystemIP: state.form.subSystemIP,
subSystemIPPort: state.form.subSystemIPPort,
deviceIP: state.form.deviceIP,
deviceIPPort: state.form.deviceIPPort,
serviceName: state.form.serviceName,
}
communicationFormRef.value.open(communicationData)
}
//
const onCommunicationConfirm = (data: any) => {
state.form.subSystemIP = data.subSystemIP
state.form.subSystemIPPort = data.subSystemIPPort
state.form.deviceIP = data.deviceIP
state.form.deviceIPPort = data.deviceIPPort
state.form.serviceName = data.serviceName
}
//
const onCancel = () => {
state.showDialog = false
}
//
const onSure = () => {
formRef.value.validate(async (valid: boolean) => {
if (!valid) return
state.sureLoading = true
let res = {} as any
if (state.form.id != undefined && state.form.id > 0) {
res = await new UspscaleApi().update(state.form, { showSuccessMessage: true }).catch(() => {
state.sureLoading = false
})
} else {
res = await new UspscaleApi().add(state.form, { showSuccessMessage: true }).catch(() => {
state.sureLoading = false
})
}
state.sureLoading = false
if (res?.success) {
eventBus.emit('refreshRoom')
state.showDialog = false
}
})
}
defineExpose({
open,
})
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<MyLayout>
<el-card v-show="state.showQuery" class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form :inline="true" label-width="auto" @submit.stop.prevent>
<el-form-item label="关键词">
<el-input v-model="state.filter.keyWord" placeholder="设备编号、资产编号" @keyup.enter="onQuery" />
</el-form-item>
<el-form-item label="设备型号">
<el-select v-model="state.filter.model" placeholder="请选择设备型号" clearable style="width: 150px">
<el-option
v-for="model in state.modelOptions"
:key="model.value"
:label="model.label"
:value="model.value"
/>
</el-select>
</el-form-item>
<el-form-item label="房间">
<el-select v-model="state.filter.roomID" placeholder="请选择房间" clearable style="width: 150px">
<el-option
v-for="room in state.roomOptions"
:key="room.id"
:label="room.roomName"
:value="room.id"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="state.filter.stDate"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="state.filter.edDate"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
<el-button v-auth="'api:admin:usp-scale:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增 </el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<div class="my-tools-box mb8 my-flex my-flex-between">
<div>
</div>
</div>
<el-table
v-if="state.showUspscaleList"
:data="state.uspscaleListData"
style="width: 100%"
v-loading="state.loading"
row-key="id"
default-expand-all
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deviceNo" label="设备编号" min-width="100" show-overflow-tooltip />
<el-table-column prop="assetNo" label="资产编号" min-width="100" show-overflow-tooltip />
<el-table-column prop="model" label="型号" min-width="90" show-overflow-tooltip>
<template #default="{ row }">
{{ getModelName(row.model) }}
</template>
</el-table-column>
<el-table-column prop="specification" label="规格" min-width="80" show-overflow-tooltip />
<el-table-column prop="roomID" label="房间" width="120" align="center" show-overflow-tooltip>
<template #default="{ row }">
{{ getRoomName(row.roomID) }}
</template>
</el-table-column>
<el-table-column label="维护标记" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="warning" v-if="row.maintenanceFlag">维护中</el-tag>
<el-tag type="success" v-else>正常</el-tag>
</template>
</el-table-column>
<el-table-column label="补料地秤" width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="primary" v-if="row.isFeedingScale"></el-tag>
<el-tag type="info" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.enabled">启用</el-tag>
<el-tag type="danger" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button
v-if="auth('api:admin:usp-scale:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:usp-scale:soft-delete')"
icon="ele-Delete"
size="small"
text
type="danger"
@click="onDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="state.pageInput.currentPage"
v-model:page-size="state.pageInput.pageSize"
:total="state.total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="onSizeChange"
@current-change="onCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<uspscale-form ref="uspscaleFormRef" :title="state.uspscaleFormTitle"></uspscale-form>
</MyLayout>
</template>
<script lang="ts" setup name="admin/uspscale">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { UspscaleGetListOutput, PageInputUspscaleGetPageInput, UspscaleGetPageOutput, RoomGetPageOutput, PageInputRoomGetPageInput } from '/@/api/admin/data-contracts'
import { UspscaleApi } from '/@/api/admin/UspscaleApi'
import { RoomApi } from '/@/api/admin/Room'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
//
const UspscaleForm = defineAsyncComponent(() => import('./components/uspscale-form.vue'))
const { proxy } = getCurrentInstance() as any
const uspscaleFormRef = ref()
const state = reactive({
loading: false,
uspscaleFormTitle: '',
filter: {
keyWord: '',
model: '',
roomID: undefined as number | undefined,
stDate: '',
edDate: '',
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as PageInputUspscaleGetPageInput,
uspscaleListData: [] as Array<UspscaleGetPageOutput>,
roomOptions: [] as Array<RoomGetPageOutput>,
modelOptions: [
{ label: 'CAIS2-U 1500kg', value: '001' },
{ label: 'METTLER', value: '3' },
{ label: 'OHAUS', value: '5' }
],
showQuery: true,
showUspscaleList: true,
})
onMounted(() => {
getRoomOptions()
Query()
eventBus.off('refreshRoom')
eventBus.on('refreshRoom', () => {
Query()
})
})
onBeforeMount(() => {
eventBus.off('refreshRoom')
})
const onChangeUspscaleList = () => {
state.showUspscaleList = !state.showUspscaleList
if (state.showUspscaleList) {
Query()
}
}
const onQuery = () => {
Query()
}
const getRoomOptions = async () => {
try {
const roomPageInput = {
currentPage: 1,
pageSize: 1000, //
filter: {
keyWord: '',
}
} as PageInputRoomGetPageInput
const res = await new RoomApi().getPage(roomPageInput)
if (res?.success) {
state.roomOptions = res.data?.list ?? []
}
} catch (error) {
console.error('获取房间列表失败:', error)
}
}
const getRoomName = (roomID: number) => {
const room = state.roomOptions.find(r => r.id === roomID)
return room ? room.roomName : `未知房间(${roomID})`
}
const getModelName = (modelValue: string) => {
const model = state.modelOptions.find(m => m.value === modelValue)
return model ? model.label : modelValue
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
const res = await new UspscaleApi().getPage(state.pageInput).catch(() => {
state.loading = false
})
state.uspscaleListData = res?.data?.list ?? []
state.total = res?.data?.total ?? 0
state.loading = false
}
const onAdd = () => {
state.uspscaleFormTitle = '新增地秤'
uspscaleFormRef.value.open({
id: 0,
enabled: true,
deviceNo: '',
assetNo: '',
model: '',
specification: '',
roomID: undefined,
principalId: undefined,
maintenanceFlag: false,
warningLowerLimit: 0,
subSystemIP: '',
subSystemIPPort: '',
deviceIP: '',
deviceIPPort: '',
scalePrecision: 0,
serviceName: '',
isFeedingScale: false
})
}
const onEdit = (row: UspscaleGetListOutput) => {
state.uspscaleFormTitle = '编辑地秤'
uspscaleFormRef.value.open(row)
}
const onDelete = (row: UspscaleGetListOutput) => {
proxy.$modal
.confirmDelete(`确定要删除设备【${row.deviceNo || row.assetNo || '未知设备'}】?`)
.then(async () => {
await new UspscaleApi().softDelete({ id: row.id }, { loading: true })
Query()
})
.catch(() => {})
}
const onSizeChange = (val: number) => {
state.pageInput.currentPage = 1
state.pageInput.pageSize = val
onQuery()
}
const onCurrentChange = (val: number) => {
state.pageInput.currentPage = val
onQuery()
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,51 @@
<template>
<div class="bio-bga-container">
<h1 class="page-title">{{ pageTitle }}</h1>
<el-card class="content-card" shadow="hover">
<template #header>
<div class="card-header">
<span>BGA 模拟数据</span>
</div>
</template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="sample" label="样本" />
<el-table-column prop="value" label="测量值" />
<el-table-column prop="status" label="状态" />
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts" name="BioTestBga">
import { ref } from 'vue'
import { bgaDataList, BgaDataItem } from './mock'
const pageTitle = 'BGA 测试界面'
const tableData = ref<BgaDataItem[]>(bgaDataList)
</script>
<style scoped lang="scss">
.bio-bga-container {
padding: 20px;
background-color: #f0f2f5;
min-height: calc(100vh - 50px);
}
.page-title {
font-size: 24px;
font-weight: bold;
color: #303133;
margin-bottom: 20px;
}
.content-card {
.card-header {
font-size: 18px;
font-weight: 500;
}
.el-table {
margin-top: 10px;
}
}
</style>

View File

@ -0,0 +1,14 @@
// Mock data for BGA test page
export interface BgaDataItem {
id: number
sample: string
value: number
status: string
}
export const bgaDataList: BgaDataItem[] = [
{ id: 1, sample: 'Sample A', value: 42, status: 'OK' },
{ id: 2, sample: 'Sample B', value: 37, status: 'OK' },
{ id: 3, sample: 'Sample C', value: 50, status: 'Warning' },
{ id: 4, sample: 'Sample D', value: 28, status: 'Fail' },
]