add 培养方案页面

This commit is contained in:
Asoka.Wang 2025-06-17 23:24:42 +08:00
parent f42b22dc0a
commit acd9fcf076
9 changed files with 1046 additions and 366 deletions

View File

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

View File

@ -1,4 +1,4 @@
import { ReactorPageInput, ReactorPageResponse, ReactorOutput, ReactorAddInput, ReactorUpdateInput, ReactorTypeEnumListOutput } from '/@/api/types/ReactorType'
import { ReactorPageInput, ReactorPageResponse, ReactorOutput, ReactorAddInput, ReactorUpdateInput, ReactorTypeEnumListOutput } from '/@/api/types/reactorType'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'

View File

@ -75,8 +75,10 @@ export interface ReactorDto {
createdTime?: string | null
/** 修改时间 */
modifiedTime?: string | null
/** 压力单位 */
pressureUnit?: string | null
/** 压力单位 */
pressureUnit?: string | null
/** 是否外置泵 */
isExternalPump?: boolean
}
/** 反应器添加和更新输入接口 */

View File

@ -0,0 +1,146 @@
import { ServiceResponse } from './response'
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤条件
export interface CultureProtocolFilter {
/** 设备编号 */
keyWord?: string | null
/** 开始时间 */
stDate?: string | null
/** 结束时间 */
edDate?: string | null
/** 反应器Id */
equReactorId?: number | null
}
/**
*
*/
export interface CultureProtocolPageDto {
/** 主键ID */
id: number
/** 培养方案名称 */
cultureProtocolName: 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
}
export interface CultureProtocolDto {
/** 主键ID */
id: number
/** 培养方案名称 */
cultureProtocolName: 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 */
cultureProtocolId: number
/** 培养开始天数 */
cultureDayStarting: number
/** 培养结束天数 */
cultureDayEnding: number
/** 下限值 */
lowerLimit: number
/** 补料目标值 */
feedingTo: number
/** 下限操作符 */
lowerLimitOperator: string
/** 计算模式 */
calculationMode: number
}
/** 补料任务 */
export interface FeedingTask {
/** 主键ID */
id: number
/** 培养方案ID */
cultureProtocolId: number
/** 补料泵编号 */
feedingPumpNo: number
/** 培养天数 */
day: number
/** 补料时间 */
feedingTime: string
/** 补料类型 */
feedingType: number
/** 补料体积百分比 */
feedingVolumePercent: number
/** 补料体积 */
feedingVolume: number
}
/** 固定补料泵配置 */
export interface FixedFeedingPump {
/** 主键ID */
id: number
/** 培养方案ID */
cultureProtocolId?: number
/** 配置ID */
configId?: number
/** 补料泵编号 */
feedingPumpNo: number
/** 物料定义袋ID */
itemDefBagID?: number
/** 物料定义培养基ID */
itemDefFeedingMediumID?: number
/** 设备泵ID */
equPumpId?: number
}
export type CultureProtocolPageInput = ServiceRequestPage<CultureProtocolFilter>
export type CultureProtocolPageResponse = ServiceResponse<PageResponse<CultureProtocolPageDto>>;
export type CultureProtocolOutput = ServiceResponse<CultureProtocolDto>;
export type CultureProtocolAddInput = CultureProtocolDto;
export type CultureProtocolUpdateInput = CultureProtocolDto;

View File

@ -1,5 +1,5 @@
import { ServiceResponse } from './response';
import { ServiceRequstPage } from './pageInput'
import { ServiceRequestPage } from './pageInput'
import { PageResponse } from './pageResponse'
// 过滤
@ -93,7 +93,7 @@ export interface RevokeTypeEnumItem {
label: string
}
export type RevokeReasonPageInput = ServiceRequstPage<RevokeReasonFilter>;
export type RevokeReasonPageInput = ServiceRequestPage<RevokeReasonFilter>;
export type RevokeReasonPageResponse = ServiceResponse<PageResponse<RevokeReasonPageDto>>;
export type RevokeReasonOutput = ServiceResponse<RevokeReasonDto[]>;

View File

@ -0,0 +1,515 @@
<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="65%" append-to-body>
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<el-tabs v-model="state.activeTab">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="basic">
<div class="form-section">
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="方案名称" prop="cultureProtocolName"
:rules="[{ required: true, message: '请输入方案名称', trigger: ['blur', 'change'] }]">
<el-input v-model="form.cultureProtocolName" clearable placeholder="请输入方案名称" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="培养天数" prop="days"
:rules="[{ required: true, message: '请输入培养天数', trigger: ['blur', 'change'] }]">
<el-input-number v-model="form.days" :min="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="反应器" prop="equReactorId"
:rules="[{ required: true, message: '请选择反应器', trigger: ['blur', 'change'] }]">
<el-select v-model="form.equReactorId" placeholder="请选择反应器" style="width: 100%">
<el-option v-for="item in state.reactorOptions" :key="item.id" :label="item.name"
:value="item.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="equFeedingScaleId"
:rules="[{ required: true, message: '请选择补料秤', trigger: ['blur', 'change'] }]">
<el-select v-model="form.equFeedingScaleId" placeholder="请选择补料秤" style="width: 100%">
<el-option v-for="item in state.scaleOptions" :key="item.id" :label="item.name"
:value="item.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="initialWorkingVolume"
:rules="[{ required: true, message: '请输入初始工作体积', trigger: ['blur', 'change'] }]">
<el-input-number v-model="form.initialWorkingVolume" :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="转种细胞体积" prop="transferCellVolume"
:rules="[{ required: true, message: '请输入转种细胞体积', trigger: ['blur', 'change'] }]">
<el-input-number v-model="form.transferCellVolume" :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="葡萄糖补料验证">
<el-switch v-model="form.glucoseFeedingVerification" active-text="是" inactive-text="否" />
</el-form-item>
</el-col>
<el-col :xs="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-tab-pane>
<!-- 固定补料泵 -->
<el-tab-pane label="固定补料泵" name="pumps">
<div class="form-section">
<el-table :data="form.fixedFeedingPumps" border style="width: 100%">
<el-table-column label="补料泵编号" prop="feedingPumpNo" width="120">
<template #default="scope">
<el-input v-model="scope.row.feedingPumpNo" disabled />
</template>
</el-table-column>
<el-table-column
v-if="state.form.equReactorId && state.reactorOptions.find(r => r.id === state.form.equReactorId)?.needExternalPump === true"
label="外置泵"
prop="equPumpId"
min-width="200"
>
<template #default="scope">
<el-select v-model="scope.row.equPumpId" placeholder="请选择外置泵" style="width: 100%">
<el-option
v-for="item in state.pumpOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="物料定义袋" prop="itemDefBagID" min-width="200">
<template #default="scope">
<el-select v-model="scope.row.itemDefBagID" placeholder="请选择物料定义袋" style="width: 100%">
<el-option
v-for="item in state.bagOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="补料培养基" prop="itemDefFeedingMediumID" min-width="200">
<template #default="scope">
<el-select v-model="scope.row.itemDefFeedingMediumID" placeholder="请选择补料培养基" style="width: 100%">
<el-option
v-for="item in state.mediumOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<!-- 补料任务 -->
<el-tab-pane label="补料任务" name="tasks">
<div class="form-section">
<div v-for="(task, index) in form.feedingTasks" :key="index" class="task-item">
<el-row :gutter="25">
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '补料泵编号' : ''"
:prop="'feedingTasks.' + index + '.feedingPumpNo'">
<el-input-number v-model="task.feedingPumpNo" :min="1" style="width: 100%" placeholder="泵编号" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '培养天数' : ''" :prop="'feedingTasks.' + index + '.day'">
<el-input-number v-model="task.day" :min="1" style="width: 100%" placeholder="天数" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '补料时间' : ''" :prop="'feedingTasks.' + index + '.feedingTime'">
<el-time-picker v-model="task.feedingTime" format="HH:mm" style="width: 100%"
placeholder="补料时间" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '补料体积' : ''" :prop="'feedingTasks.' + index + '.feedingVolume'">
<el-input-number v-model="task.feedingVolume" :min="0" :precision="2" style="width: 100%"
placeholder="补料体积" />
</el-form-item>
</el-col>
</el-row>
<el-button type="danger" icon="ele-Delete" circle @click="removeTask(index)"
v-if="form.feedingTasks.length > 1" />
</div>
<el-button type="primary" icon="ele-Plus" @click="addTask">添加任务</el-button>
</div>
</el-tab-pane>
<!-- 自动葡萄糖补料规则 -->
<el-tab-pane label="自动葡萄糖补料规则" name="glucose">
<div class="form-section">
<div v-for="(rule, index) in form.autoGlucoseFeedingRules" :key="index" class="rule-item">
<el-row :gutter="25">
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '培养开始天数' : ''"
:prop="'autoGlucoseFeedingRules.' + index + '.cultureDayStarting'">
<el-input-number v-model="rule.cultureDayStarting" :min="0" style="width: 100%"
placeholder="开始天数" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '培养结束天数' : ''"
:prop="'autoGlucoseFeedingRules.' + index + '.cultureDayEnding'">
<el-input-number v-model="rule.cultureDayEnding" :min="0" style="width: 100%"
placeholder="结束天数" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '下限值' : ''"
:prop="'autoGlucoseFeedingRules.' + index + '.lowerLimit'">
<el-input-number v-model="rule.lowerLimit" :min="0" :precision="2" style="width: 100%"
placeholder="下限值" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item :label="index === 0 ? '补料目标值' : ''"
:prop="'autoGlucoseFeedingRules.' + index + '.feedingTo'">
<el-input-number v-model="rule.feedingTo" :min="0" :precision="2" style="width: 100%"
placeholder="目标值" />
</el-form-item>
</el-col>
</el-row>
<el-button type="danger" icon="ele-Delete" circle @click="removeRule(index)"
v-if="form.autoGlucoseFeedingRules.length > 1" />
</div>
<el-button type="primary" icon="ele-Plus" @click="addRule">添加规则</el-button>
</div>
</el-tab-pane>
</el-tabs>
</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="admin/culture-protocol/form">
import { reactive, toRefs, ref, getCurrentInstance, onMounted } from 'vue'
import { CultureProtocolApi } from '/@/api/admin/CultureProtocolApi'
import { ReactorApi } from '/@/api/admin/ReactorApi'
import { UspscaleApi } from '/@/api/admin/UspscaleApi'
import { ItemDefFeedingMediumApi } from '/@/api/admin/item-def-feeding-medium'
import { ItemDefBagApi } from '/@/api/admin/item-def-bag'
import type { CultureProtocolDto } from '/@/api/types/cultureprotocol'
const { proxy } = getCurrentInstance() as any
defineProps({
title: {
type: String,
default: '',
},
})
const emit = defineEmits<{
(e: 'ok'): void
}>()
const formRef = ref()
const state = reactive({
showDialog: false,
sureLoading: false,
activeTab: 'basic',
form: {
id: 0,
cultureProtocolName: '',
equReactorId: undefined,
equFeedingScaleId: undefined,
days: 0,
initialWorkingVolume: 0,
initialWorkingMediumId: 0,
transferCellVolume: 0,
transferCellTypeId: 0,
glucoseFeedingVerification: true,
enabled: true,
remark: '',
autoGlucoseFeedingRules: [],
feedingTasks: [],
fixedFeedingPumps: []
} as CultureProtocolDto,
reactorOptions: [] as Array<{ id: number; name: string; needExternalPump: boolean }>,
scaleOptions: [] as Array<{ id: number; name: string }>,
mediumOptions: [] as Array<{ id: number; name: string }>,
cellTypeOptions: [],
bagOptions: [] as Array<{ id: number; name: string }>,
pumpOptions: [] as Array<{ id: number; name: string }>,
})
const { form } = toRefs(state)
onMounted(() => {
getReactorOptions()
getScaleOptions()
getMediumOptions()
getBagOptions()
})
//
const open = async (row: any = {}, isCopy = false) => {
proxy.$modal.loading()
if (row.id > 0) {
const res = await new CultureProtocolApi().get({ id: row.id }, { loading: true })
if (res?.success) {
let formData = res.data as CultureProtocolDto
if (isCopy) {
formData.id = 0
formData.cultureProtocolName = formData.cultureProtocolName + ' - 副本'
}
state.form = formData
}
} else {
state.form = {
id: 0,
cultureProtocolName: '',
equReactorId: undefined,
equFeedingScaleId: undefined,
days: 0,
initialWorkingVolume: 0,
initialWorkingMediumId: 0,
transferCellVolume: 0,
transferCellTypeId: 0,
glucoseFeedingVerification: true,
remark: '',
autoGlucoseFeedingRules: [],
feedingTasks: [],
fixedFeedingPumps: defaultPumps
}
}
state.showDialog = true
proxy.$modal.closeLoading()
}
//
const addRule = () => {
state.form.autoGlucoseFeedingRules.push({
id: 0,
cultureProtocolId: 0,
cultureDayStarting: 0,
cultureDayEnding: 0,
lowerLimit: 0,
feedingTo: 0,
lowerLimitOperator: '',
calculationMode: 0
})
}
//
const removeRule = (index: number) => {
state.form.autoGlucoseFeedingRules.splice(index, 1)
}
//
const addTask = () => {
state.form.feedingTasks.push({
id: 0,
cultureProtocolId: 0,
feedingPumpNo: 0,
day: 0,
feedingTime: '',
feedingType: 0,
feedingVolumePercent: 0,
feedingVolume: 0
})
}
//
const removeTask = (index: number) => {
state.form.feedingTasks.splice(index, 1)
}
//
const onSure = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
state.sureLoading = true
try {
const api = new CultureProtocolApi()
const res = state.form.id
? await api.update(state.form)
: await api.add(state.form)
if (res.success) {
proxy.$modal.msgSuccess(state.form.id ? '修改成功' : '新增成功')
state.showDialog = false
emit('ok')
}
} catch (error) {
console.error(state.form.id ? '修改失败:' : '新增失败:', error)
} finally {
state.sureLoading = false
}
}
})
}
//
const onCancel = () => {
state.showDialog = false
reset()
}
//
const reset = () => {
if (formRef.value) {
formRef.value.resetFields()
}
state.form = {
id: 0,
cultureProtocolName: '',
equReactorId: undefined,
equFeedingScaleId: undefined,
days: 0,
initialWorkingVolume: 0,
initialWorkingMediumId: 0,
transferCellVolume: 0,
transferCellTypeId: 0,
glucoseFeedingVerification: true,
remark: '',
autoGlucoseFeedingRules: [],
feedingTasks: [],
fixedFeedingPumps: defaultPumps
}
}
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 ?? '',
needExternalPump: item.isExternalPump === true
})) ?? []
}
} catch (error) {
console.error('获取反应器列表失败:', error)
}
}
const getScaleOptions = async () => {
const res = await new UspscaleApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.scaleOptions = res.data?.list?.filter(item => item.isFeedingScale === true).map(item => ({
id: item.id ?? 0,
name: item.deviceNo ?? ''
})) ?? []
}
}
//
const getMediumOptions = async () => {
const res = await new ItemDefFeedingMediumApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.mediumOptions = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.mediumName ?? ''
})) ?? []
}
}
//
const getBagOptions = async () => {
const res = await new ItemDefBagApi().getPage({
currentPage: 1,
pageSize: 10000
})
if (res?.success) {
state.bagOptions = res.data?.list?.map(item => ({
id: item.id ?? 0,
name: item.itemDescription ?? ''
})) ?? []
}
}
const defaultPumps = [
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 1, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 2, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 3, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 4, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 5, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 6, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 7, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined },
{ id: 0, cultureProtocolId: undefined, configId: undefined, feedingPumpNo: 8, itemDefBagID: undefined, itemDefFeedingMediumID: undefined, equPumpId: undefined }
]
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 20px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 20px;
color: #303133;
}
}
.rule-item,
.task-item,
.pump-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
.el-button {
margin-left: 10px;
margin-top: 32px;
}
}
.dialog-footer {
text-align: right;
}
.mt-2 {
margin-top: 8px;
}
</style>

View File

@ -0,0 +1,285 @@
<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:culture-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="cultureProtocolName" label="方案名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="days" label="培养天数" min-width="100" show-overflow-tooltip />
<el-table-column prop="initialWorkingVolume" label="初始工作体积" min-width="120" show-overflow-tooltip />
<el-table-column label="葡萄糖补料验证" width="150" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="success" v-if="row.glucoseFeedingVerification"></el-tag>
<el-tag type="info" 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="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:culture-protocol:update')"
icon="ele-EditPen"
size="small"
text
type="primary"
@click="onEdit(row)"
>编辑</el-button
>
<el-button
v-if="auth('api:admin:culture-protocol:copy')"
icon="ele-CopyDocument"
size="small"
text
type="success"
@click="onCopy(row)"
>复制</el-button
>
<el-button
v-if="auth('api:admin:culture-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>
<culture-protocol-form ref="protocolFormRef" :title="state.protocolFormTitle" @ok="Query" />
</MyLayout>
</template>
<script lang="ts" setup name="admin/culture-protocol">
import { ref, reactive, onMounted, getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
import { CultureProtocolApi } from '/@/api/admin/CultureProtocolApi'
import type { CultureProtocolDto, CultureProtocolPageInput, CultureProtocolPageDto } from '/@/api/types/cultureprotocol'
import eventBus from '/@/utils/mitt'
import { auth } from '/@/utils/authFunction'
import { ReactorApi } from '/@/api/admin/ReactorApi'
//
const CultureProtocolForm = defineAsyncComponent(() => import('./components/culture-protocol-form.vue'))
const { proxy } = getCurrentInstance() as any
const protocolFormRef = ref()
const state = reactive({
loading: false,
protocolFormTitle: '',
filter: {
keyWord: '',
stDate: '',
edDate: '',
equReactorId: null as number | null
},
total: 0,
pageInput: {
currentPage: 1,
pageSize: 20,
} as CultureProtocolPageInput,
protocolListData: [] as Array<CultureProtocolPageDto>,
showQuery: true,
showProtocolList: true,
reactorOptions: [] as Array<{ id: number; name: string }>,
})
onMounted(() => {
Query()
getReactorOptions()
})
const onQuery = () => {
Query()
}
const Query = async () => {
state.loading = true
state.pageInput.filter = state.filter
const res = await new CultureProtocolApi().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: CultureProtocolDto) => {
state.protocolFormTitle = '编辑培养方案'
protocolFormRef.value?.open(row)
}
const onCopy = (row: CultureProtocolDto) => {
state.protocolFormTitle = '复制培养方案'
protocolFormRef.value?.open(row, true)
}
const onDelete = (row: CultureProtocolDto) => {
proxy.$modal.confirm('确认要删除该记录吗?').then(async () => {
try {
const res = await new CultureProtocolApi().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 ?? ''
})) ?? []
}
} catch (error) {
console.error('获取反应器列表失败:', error)
}
}
const getReactorDescription = async (reactorId: number) => {
return state.reactorOptions.find(item => item.id === reactorId)?.name ?? ''
}
const onReset = () => {
state.filter = {
keyWord: '',
stDate: '',
edDate: '',
equReactorId: null
}
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-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

@ -1,137 +0,0 @@
<template>
<el-dialog v-model="visible" :title="title" width="500px" append-to-body destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="反应器名称" prop="reactorName">
<el-input v-model="form.reactorName" placeholder="请输入反应器名称" />
</el-form-item>
<el-form-item label="反应器类型" prop="reactorType">
<el-select v-model="form.reactorType" placeholder="请选择反应器类型" style="width: 100%">
<el-option v-for="item in reactorTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { ReactorApi } from '../../../../api/admin/ReactorApi'
import type { ReactorDto, ReactorTypeEnumItem } from '../../../../api/types/reactorType'
const props = defineProps<{
title: string
}>()
const emit = defineEmits<{
(e: 'ok'): void
}>()
const visible = ref(false)
const formRef = ref()
const reactorTypeOptions = ref<ReactorTypeEnumItem[]>([])
const form = reactive<ReactorDto>({
reactorName: '',
reactorType: '',
status: true
})
const rules = {
reactorName: [
{ required: true, message: '请输入反应器名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
reactorType: [
{ required: true, message: '请选择反应器类型', trigger: 'change' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
]
}
/** 获取反应器类型枚举列表 */
const getReactorTypeOptions = async () => {
try {
const res = await new ReactorApi().getReactorTypeEnumList()
reactorTypeOptions.value = res.data ?? []
} catch (error) {
console.error('获取反应器类型枚举列表失败:', error)
}
}
/** 打开弹窗 */
const open = (row?: ReactorDto) => {
visible.value = true
if (row) {
Object.assign(form, row)
}
}
/** 提交表单 */
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
try {
const api = new ReactorApi()
const res = form.id
? await api.update(form)
: await api.add(form)
if (res.success) {
ElMessage.success(form.id ? '修改成功' : '新增成功')
visible.value = false
emit('ok')
}
} catch (error) {
console.error(form.id ? '修改失败:' : '新增失败:', error)
}
}
})
}
/** 取消按钮 */
const cancel = () => {
visible.value = false
reset()
}
/** 重置表单 */
const reset = () => {
if (formRef.value) {
formRef.value.resetFields()
}
Object.assign(form, {
id: undefined,
reactorName: '',
reactorType: '',
status: true
})
}
onMounted(() => {
getReactorTypeOptions()
})
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.dialog-footer {
text-align: right;
}
</style>

View File

@ -1,224 +0,0 @@
<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="reactorName">
<el-input v-model="state.filter.keyWord" placeholder="请输入反应器名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="反应器类型" prop="reactorType">
<el-select v-model="state.filter.reactorType" placeholder="请选择反应器类型" clearable style="width: 200px">
<el-option v-for="item in state.reactorTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="state.filter.status" placeholder="请选择状态" clearable style="width: 200px">
<el-option label="启用" :value="true" />
<el-option label="禁用" :value="false" />
</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: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="reactorName" label="反应器名称" min-width="120" show-overflow-tooltip />
<el-table-column label="反应器类型" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getReactorTypeName(row.reactorType) }}
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.status ? 'success' : 'danger'">
{{ row.status ? '启用' : '禁用' }}
</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="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:reactor:update'" icon="ele-EditPen" size="small" text type="primary"
@click="handleEdit(row)">编辑</el-button>
<el-button v-auth="'api:admin: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" />
</my-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ReactorApi } from '../../../api/admin/ReactorApi'
import eventBus from '/@/utils/mitt'
import type {
ReactorPageInput,
ReactorDto,
ReactorTypeEnumItem
} from '../../../api/types/reactorType'
import ReactorForm from './components/reactor-form.vue'
const loading = ref(false)
const formRef = ref()
const state = reactive({
loading: false,
formTitle: '',
total: 0,
filter: {
keyWord: "",
reactorType: "",
status: null,
startTime: "",
endTime: ""
},
pageInput: {
currentPage: 1,
pageSize: 20,
} as ReactorPageInput,
reactorList: [] as Array<ReactorDto>,
reactorTypeOptions: [] as ReactorTypeEnumItem[]
})
/** 查询列表 */
const getList = async () => {
loading.value = true
state.pageInput.filter = state.filter
try {
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 getReactorTypeOptions = async () => {
try {
const res = await new ReactorApi().getReactorTypeEnumList()
state.reactorTypeOptions = res.data ?? []
} catch (error) {
console.error('获取反应器类型枚举列表失败:', error)
}
}
const getReactorTypeName = (reactorType: string) => {
const item = state.reactorTypeOptions.find(item => item.value === Number(reactorType))
return item?.label || ''
}
/** 搜索按钮操作 */
const handleQuery = () => {
state.pageInput.currentPage = 1
getList()
}
/** 新增按钮操作 */
const handleAdd = () => {
state.formTitle = '新增反应器'
formRef.value?.open()
}
/** 修改按钮操作 */
const handleEdit = (row: ReactorDto) => {
state.formTitle = '编辑反应器'
formRef.value?.open(row)
}
/** 删除按钮操作 */
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()
}
onMounted(() => {
getReactorTypeOptions()
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>