feat:配方配置-配置弹框对接接口

main
黄伟杰 4 weeks ago
parent 110f99f8d3
commit 3649df0547

@ -177,6 +177,10 @@ export const RecipeConfigApi = {
return await request.get({ url: `/iot/recipe-device-attribute/page`, params })
},
updateRecipeDeviceAttribute: async (data: { recipeId: string | number; ids: number[] }) => {
return await request.put({ url: `/iot/recipe-device-attribute/update`, data })
},
saveRecipePointConfig: async (data: { recipeId: string | number; attributeIds: number[] }) => {
ensureMockSeeded()
await sleep(120)

@ -21,6 +21,9 @@ v-model="queryParams.productName" placeholder="请输入产品名称" clearable
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
<el-button type="primary" plain @click="openDialog('create')">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
@ -49,7 +52,7 @@ ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-to
<el-table-column label="操作" align="center" width="240px" fixed="right">
<template #default="scope">
<el-button link type="primary" @click.stop="openDetail(scope.row)">配置</el-button>
<el-button link type="warning" @click.stop="openDialog('update', scope.row)">编辑</el-button>
<el-button link type="primary" @click.stop="openDialog('update', scope.row)">编辑</el-button>
<el-button link type="danger" @click.stop="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
@ -60,15 +63,9 @@ ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-to
</ContentWrap>
<RecipeDetailList
ref="detailRef"
:visible="detailVisible"
:recipe-id="detailMeta.recipeCode"
:manual-recipe-id="detailMeta.id"
:recipe-code="detailMeta.recipeCode"
:name="detailMeta.name"
@config="handleDetailConfig"
@close="closeDetail"
/>
ref="detailRef" :visible="detailVisible"
:recipe-id="detailMeta.id ? String(detailMeta.id) : undefined" :manual-recipe-id="detailMeta.id"
:recipe-code="detailMeta.recipeCode" :name="detailMeta.name" @config="handleDetailConfig" @close="closeDetail" />
<Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form ref="dialogFormRef" :model="dialogForm" :rules="dialogRules" label-width="100px" v-loading="dialogLoading">
@ -111,9 +108,59 @@ v-model="dialogForm.machineName" placeholder="请选择关联设备" clearable f
<Dialog title="配置" v-model="configVisible" width="920px">
<div v-loading="configLoading">
<el-transfer
class="formula-config-transfer" v-model="configSelectedKeys" :data="configCandidates" filterable
:titles="['来源', '目标']" />
<div class="formula-config-picklist">
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>来源</div>
<div class="formula-config-panel__meta">{{ filteredSourceItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="sourceKeyword" placeholder="筛选" clearable />
</div>
<el-table
class="formula-config-panel__table"
:data="filteredSourceItems"
height="440"
:show-header="false"
row-key="key"
@selection-change="handleSourceSelectionChange"
:row-class-name="getSourceRowClass"
>
<el-table-column type="selection" width="44" :selectable="isSourceSelectable" />
<el-table-column prop="label" />
</el-table>
</div>
<div class="formula-config-actions">
<el-button type="primary" :disabled="!sourceCheckedKeys.length" @click="addToTarget">
&gt;&gt;
</el-button>
<el-button :disabled="!targetCheckedKeys.length" @click="removeFromTarget">
&lt;&lt;
</el-button>
</div>
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>目标</div>
<div class="formula-config-panel__meta">{{ filteredTargetItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="targetKeyword" placeholder="筛选" clearable />
</div>
<el-table
class="formula-config-panel__table"
:data="filteredTargetItems"
height="440"
:show-header="false"
row-key="key"
@selection-change="handleTargetSelectionChange"
>
<el-table-column type="selection" width="44" />
<el-table-column prop="label" />
</el-table>
</div>
</div>
</div>
<template #footer>
<el-button @click="configVisible = false"> </el-button>
@ -138,6 +185,7 @@ const message = useMessage()
const { t } = useI18n()
const loading = ref(false)
const queryFormRef = ref()
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -184,6 +232,11 @@ const handleQuery = () => {
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields?.()
handleQuery()
}
const handlePagination = () => {
getList()
}
@ -342,19 +395,23 @@ const detailVisible = ref(false)
const detailMeta = reactive({
id: undefined as number | undefined,
recipeCode: '',
name: ''
name: '',
deviceId: undefined as number | undefined,
machineName: ''
})
const openDetail = async (row: RecipeConfigVO) => {
detailMeta.id = row.id
detailMeta.recipeCode = row.recipeCode
detailMeta.name = row.name ?? row.recipeName ?? ''
detailMeta.deviceId = row.deviceId
detailMeta.machineName = row.machineName ?? row.deviceName ?? ''
detailVisible.value = true
}
const handleDetailConfig = () => {
if (!detailMeta.recipeCode) return
openConfigDialog(detailMeta.recipeCode)
if (!detailMeta.id) return
openConfigDialog(detailMeta.id)
}
const handleRowClick = async (row: RecipeConfigVO, column: any) => {
@ -374,34 +431,136 @@ type TransferItem = { key: number; label: string; disabled?: boolean }
const configVisible = ref(false)
const configLoading = ref(false)
const configRecipeId = ref<string | undefined>(undefined)
const configRecipeId = ref<string | number | undefined>(undefined)
const configCandidates = ref<TransferItem[]>([])
const configSelectedKeys = ref<number[]>([])
const configSelectedItems = ref<TransferItem[]>([])
const sourceKeyword = ref('')
const targetKeyword = ref('')
const sourceCheckedKeys = ref<number[]>([])
const targetCheckedKeys = ref<number[]>([])
const configDeviceLabel = computed(() => {
const name = detailMeta.machineName?.trim()
if (name) return name
if (detailMeta.deviceId !== undefined && detailMeta.deviceId !== null) return String(detailMeta.deviceId)
return '-'
})
const normalizeKey = (value: any): number | undefined => {
if (value === null || value === undefined || value === '') return undefined
const n = Number(value)
if (Number.isNaN(n)) return undefined
return n
}
const selectedKeySet = computed(() => {
return new Set<number>(configSelectedKeys.value ?? [])
})
const contains = (text: string, keyword: string) => {
const k = (keyword ?? '').trim().toLowerCase()
if (!k) return true
return (text ?? '').toLowerCase().includes(k)
}
const filteredSourceItems = computed(() => {
return (configCandidates.value ?? []).filter((it) => contains(it.label, sourceKeyword.value))
})
const filteredTargetItems = computed(() => {
const set = selectedKeySet.value
return (configCandidates.value ?? [])
.filter((it) => set.has(it.key))
.filter((it) => contains(it.label, targetKeyword.value))
})
const isSourceSelectable = (row: TransferItem) => {
return !selectedKeySet.value.has(row.key)
}
const getSourceRowClass = ({ row }: { row: TransferItem }) => {
return selectedKeySet.value.has(row.key) ? 'formula-config-row--disabled' : ''
}
const openConfigDialog = async (recipeCode: string) => {
const handleSourceSelectionChange = (rows: TransferItem[]) => {
sourceCheckedKeys.value = (rows ?? []).map((r) => r.key)
}
const handleTargetSelectionChange = (rows: TransferItem[]) => {
targetCheckedKeys.value = (rows ?? []).map((r) => r.key)
}
const addToTarget = () => {
const next = new Set<number>(configSelectedKeys.value ?? [])
for (const key of sourceCheckedKeys.value ?? []) next.add(key)
configSelectedKeys.value = Array.from(next.values())
sourceCheckedKeys.value = []
}
const removeFromTarget = () => {
const toRemove = new Set<number>(targetCheckedKeys.value ?? [])
configSelectedKeys.value = (configSelectedKeys.value ?? []).filter((k) => !toRemove.has(k))
targetCheckedKeys.value = []
}
const buildTransferLabel = (item: any) => {
return `${item.attributeName ?? item.pointName ?? ''}${item.attributeType || item.pointType ? '' + (item.attributeType ?? item.pointType) : ''}${item.dataType ? '/' + item.dataType : ''}${item.dataUnit ? ' ' + item.dataUnit : ''}${item.attributeType || item.pointType ? '' : ''}`
}
const loadConfigCandidatesByDevice = async (deviceId: number) => {
const data = await DeviceApi.getDeviceAttributePage({ pageNo: 1, pageSize: 100, deviceId })
const list = (data?.list ?? data?.data ?? []) as any[]
const deviceItems = (list ?? [])
.map((item: any) => {
const key = normalizeKey(item?.attributeId ?? item?.id)
if (key === undefined) return undefined
return {
key,
label: buildTransferLabel(item)
} as TransferItem
})
.filter((it: any) => it && typeof it.key === 'number') as TransferItem[]
const merged = new Map<number, TransferItem>()
for (const it of deviceItems) merged.set(it.key, it)
for (const it of configSelectedItems.value) {
if (!merged.has(it.key)) merged.set(it.key, it)
}
configCandidates.value = Array.from(merged.values())
}
const openConfigDialog = async (recipeId: number | string) => {
configVisible.value = true
configRecipeId.value = recipeCode
configRecipeId.value = recipeId
configSelectedKeys.value = []
configCandidates.value = []
configSelectedItems.value = []
sourceKeyword.value = ''
targetKeyword.value = ''
sourceCheckedKeys.value = []
targetCheckedKeys.value = []
configLoading.value = true
try {
const [candidateRes, selectedRes] = await Promise.all([
DeviceApi.getDeviceContactModelPage(),
RecipeConfigApi.getRecipePointDetailPage({ pageNo: 1, pageSize: 100, recipeId: recipeCode })
])
const candidateList = Array.isArray(candidateRes)
? candidateRes
: ((candidateRes as any)?.list ?? (candidateRes as any)?.data ?? [])
configCandidates.value = (candidateList ?? []).map((item: any) => ({
key: item.id,
label: `${item.attributeName ?? item.pointName ?? ''}${item.attributeType || item.pointType ? '' + (item.attributeType ?? item.pointType) : ''}${item.dataType ? '/' + item.dataType : ''}${item.dataUnit ? ' ' + item.dataUnit : ''}${item.attributeType || item.pointType ? '' : ''}`
}))
configSelectedKeys.value = (selectedRes.list ?? [])
const selectedRes = await RecipeConfigApi.getRecipePointDetailPage({ recipeId, pageNo: 1, pageSize: 100 })
const selectedList = Array.isArray(selectedRes) ? selectedRes : selectedRes?.list ?? selectedRes?.data ?? []
const selectedKeys = (selectedList ?? [])
.map((item: any) => (item.attributeId !== undefined ? item.attributeId : item.id))
.map((id: any) => normalizeKey(id))
.filter((id: any) => typeof id === 'number')
configSelectedKeys.value = selectedKeys
configSelectedItems.value = (selectedList ?? [])
.map((item: any) => ({
key: normalizeKey(item.attributeId !== undefined ? item.attributeId : item.id),
label: buildTransferLabel(item)
}))
.filter((it: any) => typeof it.key === 'number')
const deviceId = detailMeta.deviceId
if (deviceId !== undefined && deviceId !== null) {
await loadConfigCandidatesByDevice(deviceId)
} else {
configCandidates.value = configSelectedItems.value
}
} finally {
configLoading.value = false
}
@ -411,13 +570,10 @@ const submitConfig = async () => {
if (!configRecipeId.value) return
configLoading.value = true
try {
await RecipeConfigApi.saveRecipePointConfig({
recipeId: configRecipeId.value,
attributeIds: configSelectedKeys.value
})
await RecipeConfigApi.updateRecipeDeviceAttribute({ recipeId: configRecipeId.value, ids: configSelectedKeys.value })
message.success(t('common.updateSuccess'))
configVisible.value = false
if (detailVisible.value && detailMeta.recipeCode === configRecipeId.value) {
if (detailVisible.value && String(detailMeta.id) === String(configRecipeId.value)) {
await detailRef.value?.refresh?.()
}
} finally {
@ -432,20 +588,51 @@ onMounted(() => {
</script>
<style scoped>
:deep(.formula-config-transfer.el-transfer) {
--el-transfer-panel-body-height: 440px;
.formula-config-picklist {
display: flex;
width: 100%;
}
:deep(.formula-config-transfer .el-transfer-panel) {
.formula-config-panel {
width: calc((100% - 96px) / 2);
border: 1px solid var(--el-border-color);
border-radius: 4px;
overflow: hidden;
background: var(--el-bg-color);
}
:deep(.el-transfer__buttons) {
.formula-config-panel__header {
display: flex;
text-align: center;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid var(--el-border-color);
font-weight: 600;
}
.formula-config-panel__meta {
color: var(--el-text-color-secondary);
font-weight: 400;
}
.formula-config-panel__filter {
padding: 10px 12px;
border-bottom: 1px solid var(--el-border-color);
}
.formula-config-panel__table {
width: 100%;
}
.formula-config-actions {
width: 96px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
}
:deep(.formula-config-row--disabled) {
opacity: 0.55;
}
</style>

@ -169,7 +169,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
const formData = ref<any>({
id: undefined,
reportId: undefined,
planId: undefined,

Loading…
Cancel
Save