采集地址关联机床改为穿梭框:后端DTO加MachineIds+SetCollectAddress,前端el-transfer替换checkbox

main
haoliang 1 week ago
parent 2a5568ecfc
commit 74b611d6e1

@ -21,16 +21,37 @@
</div>
</template></el-table-column>
</el-table>
<el-dialog v-model="dialogVisible" :title="editingId?'编辑地址':'新增地址'" width="600px" destroy-on-close>
<el-dialog v-model="dialogVisible" :title="editingId?'编辑地址':'新增地址'" width="900px" destroy-on-close>
<el-form :model="form" label-width="100px">
<el-form-item label="名称" required><el-input v-model="form.name"/></el-form-item>
<el-form-item label="URL" required><el-input v-model="form.url"/></el-form-item>
<el-form-item label="品牌" required><el-select v-model="form.brandId"><el-option v-for="b in brandList" :key="b.id" :label="b.brandName" :value="b.id"/></el-select></el-form-item>
<!-- 关联机床区域 -->
<el-form-item label="品牌" required>
<el-select v-model="form.brandId" @change="onBrandChange">
<el-option v-for="b in brandList" :key="b.id" :label="b.brandName" :value="b.id"/>
</el-select>
</el-form-item>
<el-form-item label="关联机床">
<el-checkbox-group v-model="form.machineIds">
<el-checkbox v-for="m in machineList" :key="m.machineId" :label="m.machineId">{{m.machineName}}</el-checkbox>
</el-checkbox-group>
<el-transfer
v-model="form.machineIds"
:data="transferMachines"
:titles="['可关联机床', '已关联']"
:props="{ key: 'id', label: 'label' }"
filterable
filter-placeholder="搜索机床"
>
<template #default="{ option }">
<span>
<span :style="dotStyle(option)"></span>
{{ option.name }}
<template v-if="option.deviceCode"> ({{ option.deviceCode }})</template>
</span>
</template>
</el-transfer>
<div class="transfer-legend">
<span><span class="status-dot online"></span>在线</span>
<span><span class="status-dot offline"></span>离线</span>
<span><span class="status-dot disabled-dot"></span>停用</span>
</div>
</el-form-item>
<el-form-item label="采集间隔" required><el-input-number v-model="form.collectInterval" :min="5"/></el-form-item>
</el-form>
@ -39,35 +60,97 @@
</div>
</template>
<script setup lang="ts">
import {ref, reactive, onMounted, watch} from 'vue'
import {ref, reactive, onMounted} from 'vue'
import {useRouter} from 'vue-router'
import {ElMessage,ElMessageBox} from 'element-plus'
import request from '@/utils/request'
import {useMockMode} from '@/composables/useMockMode'
import type { ApiResponse, Brand, CollectAddress } from '@/types'
const router=useRouter();const{isMock}=useMockMode()
import type { ApiResponse, Brand, Workshop, CollectAddress } from '@/types'
const loading=ref(false);const tableData=ref<CollectAddress[]>([]);const brandList=ref<Brand[]>([])
const dialogVisible=ref(false);const submitting=ref(false);const editingId=ref<number|null>(null)
const query=reactive({brandId:undefined as number|undefined})
const form=reactive({name:'',url:'',brandId:undefined as number|undefined,collectInterval:30,machineIds:[] as number[]})
//
const machineList=ref<{ machineId:number; machineName:string }[]>([])
//
async function loadMachinesForBrandName(brandName:string){ if(!brandName){ machineList.value = []; return } const r: ApiResponse<{ items: { machineId:number; machineName:string }[] }> = await request.get('/admin/machine', { params: { brandName } }); machineList.value = r.data?.items || [] }
interface TransferMachine { id: number; name: string; label: string; deviceCode?: string; isOnline: number; isEnabled: number }
const transferMachines=ref<TransferMachine[]>([])
function resetQuery(){query.brandId=undefined;loadData()}
function goDetail(id:number){router.push((isMock.value?'/mock/collect-address/':'/collect-address/')+id)}
async function loadData(){loading.value=true;try{const r:any=await request.get('/admin/collect-address');tableData.value=r.data?.items||[]}finally{loading.value=false}}
function handleAdd(){editingId.value=null;Object.assign(form,{name:'',url:'',brandId:undefined,collectInterval:30,machineIds: []});dialogVisible.value=true}
function handleEdit(row:any){editingId.value=row.id;Object.assign(form,row);dialogVisible.value=true}
async function handleSubmit(){submitting.value=true;try{await request[editingId.value?'put':'post'](editingId.value?`/admin/collect-address/${editingId.value}`:'/admin/collect-address',{...form});ElMessage.success('保存成功');dialogVisible.value=false;loadData()}finally{submitting.value=false}}
async function handleDelete(row:any){await ElMessageBox.confirm('确定删除【'+row.name+'】?此操作不可恢复。','提示',{type:'warning'});await request.delete(`/admin/collect-address/${row.id}`);ElMessage.success('已删除');loadData()}
function handleAdd(){
editingId.value=null
Object.assign(form,{name:'',url:'',brandId:undefined,collectInterval:30,machineIds:[]})
transferMachines.value=[]
dialogVisible.value=true
}
async function handleEdit(row: any){
editingId.value=row.id
Object.assign(form,{name:row.name,url:row.url,brandId:row.brandId,collectInterval:row.collectInterval,machineIds:[]})
// 穿 +
await loadTransferData(row.brandId, row.id)
dialogVisible.value=true
}
/** 品牌切换时重新加载穿梭框 */
async function onBrandChange(){
form.machineIds=[]
await loadTransferData(form.brandId, editingId.value ?? undefined)
}
/** 加载穿梭框:同品牌下未关联其他地址的机床 + 当前地址已关联的机床 */
async function loadTransferData(brandId?: number, addressId?: number){
if(!brandId){ transferMachines.value=[]; return }
try{
//
const brandName = brandList.value.find((b: any) => b.id === brandId)?.brandName ?? ''
const rAll: any = await request.get('/admin/machine', {params: {brandName, pageSize: 999}})
const allMachines = rAll.data?.items ?? []
// ID
const freeMachines = allMachines.filter((m: any) => !m.collectAddressId || m.collectAddressId === 0)
const freeIds = new Set(freeMachines.map((m: any) => m.id))
//
let ownIds = new Set<number>()
if(addressId){
const rOwn: any = await request.get(`/admin/collect-address/${addressId}/machines`)
const items = rOwn.data?.items ?? []
ownIds = new Set(items.map((m: any) => m.machineId))
form.machineIds = items.map((m: any) => m.machineId)
}
transferMachines.value = allMachines
.filter((m: any) => freeIds.has(m.id) || ownIds.has(m.id))
.map((m: any) => ({ id: m.id, name: m.name, label: m.name, deviceCode: m.deviceCode, isOnline: m.isOnline, isEnabled: m.isEnabled }))
}catch{ transferMachines.value=[] }
}
async function handleSubmit(){
submitting.value=true
try{
await request[editingId.value?'put':'post'](editingId.value?`/admin/collect-address/${editingId.value}`:'/admin/collect-address',{...form})
ElMessage.success('保存成功');dialogVisible.value=false;loadData()
}finally{submitting.value=false}
}
async function handleDelete(row:any){
await ElMessageBox.confirm('确定删除【'+row.name+'】?此操作不可恢复。','提示',{type:'warning'})
await request.delete(`/admin/collect-address/${row.id}`)
ElMessage.success('已删除');loadData()
}
async function loadDrops(){const r:any=await request.get('/admin/brand');brandList.value=r.data?.items||[]}
/** 机床状态色点 */
function dotStyle(m: TransferMachine): Record<string, string> {
const color = !m.isEnabled ? '#f56c6c' : m.isOnline ? '#67c23a' : '#c0c4cc'
return { display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', marginRight: '6px', verticalAlign: 'middle', backgroundColor: color }
}
onMounted(()=>{loadData();loadDrops()})
//
watch(() => form.brandId, async (newVal)=>{
const b = brandList.value.find((bb:any)=> bb.id===newVal)
const brandName = b?.brandName ?? ''
await loadMachinesForBrandName(brandName)
form.machineIds = []
}, { immediate: true })
</script>
<style scoped>
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.status-dot.online { background-color: #67c23a; }
.status-dot.offline { background-color: #c0c4cc; }
.status-dot.disabled-dot { background-color: #f56c6c; }
.transfer-legend { display: flex; gap: 16px; margin-top: 8px; font-size: 12px; color: #909399; align-items: center; }
.transfer-legend .status-dot { margin-right: 4px; }
</style>

@ -5,6 +5,9 @@ namespace CncModels.Dto.CollectAddress
/// </summary>
public class CollectAddressMachineItem
{
/// <summary>机床ID</summary>
public int MachineId { get; set; }
/// <summary>机床名称</summary>
public string MachineName { get; set; }

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace CncModels.Dto.CollectAddress
{
/// <summary>
@ -9,5 +11,7 @@ namespace CncModels.Dto.CollectAddress
public string Url { get; set; }
public int BrandId { get; set; }
public int CollectInterval { get; set; }
/// <summary>关联机床ID列表</summary>
public List<int> MachineIds { get; set; }
}
}

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace CncModels.Dto.CollectAddress
{
/// <summary>
@ -9,5 +11,7 @@ namespace CncModels.Dto.CollectAddress
public string Url { get; set; }
public int BrandId { get; set; }
public int CollectInterval { get; set; }
/// <summary>关联机床ID列表全量替换</summary>
public List<int> MachineIds { get; set; }
}
}

@ -188,5 +188,14 @@ namespace CncRepository.Impl
conn.Execute(sql, param);
}
}
public void SetCollectAddress(int machineId, int? collectAddressId)
{
using (var conn = CreateConnection())
{
conn.Execute("UPDATE cnc_machine SET collect_address_id = @AddressId, updated_at = NOW() WHERE id = @Id",
new { Id = machineId, AddressId = collectAddressId });
}
}
}
}

@ -22,5 +22,7 @@ namespace CncRepository.Interface
List<Machine> GetEnabledOnline();
void UpdateOnlineStatus(int id, bool isOnline);
void UpdateLastCollect(int id, Machine entity);
/// <summary>设置机床所属的采集地址</summary>
void SetCollectAddress(int machineId, int? collectAddressId);
}
}

@ -73,7 +73,17 @@ namespace CncService.Impl
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
return _collectAddressRepository.Create(entity);
var id = _collectAddressRepository.Create(entity);
// 绑定机床
if (request.MachineIds != null)
{
foreach (var mid in request.MachineIds)
{
_machineRepository.SetCollectAddress(mid, id);
}
}
return id;
}
public bool Update(int id, UpdateCollectAddressRequest request)
@ -87,7 +97,24 @@ namespace CncService.Impl
address.BrandId = (request.BrandId != 0) ? request.BrandId : address.BrandId;
address.CollectInterval = (request.CollectInterval != 0) ? request.CollectInterval : address.CollectInterval;
address.UpdatedAt = DateTime.Now;
return _collectAddressRepository.Update(address);
_collectAddressRepository.Update(address);
// 全量替换机床关联
if (request.MachineIds != null)
{
// 先清除该地址下所有机床的关联
var currentMachines = _machineRepository.GetEnabledByAddressId(id);
foreach (var m in currentMachines)
{
_machineRepository.SetCollectAddress(m.Id, null);
}
// 再绑定新选中的机床
foreach (var mid in request.MachineIds)
{
_machineRepository.SetCollectAddress(mid, id);
}
}
return true;
}
public bool Delete(int id)
@ -117,6 +144,7 @@ namespace CncService.Impl
}
result.Add(new CollectAddressMachineItem
{
MachineId = m.Id,
MachineName = m.Name ?? m.DeviceCode,
DeviceCode = m.DeviceCode,
WorkshopName = workshopName,

Loading…
Cancel
Save