|
|
<template>
|
|
|
<div>
|
|
|
<div class="mb-16">
|
|
|
<el-button type="primary" @click="handleAdd">+ 新增工人</el-button>
|
|
|
<el-button v-if="selectedRows.length" size="default" @click="batchStatus(1)">批量启用({{ selectedRows.length }})</el-button>
|
|
|
<el-button v-if="selectedRows.length" size="default" @click="batchStatus(0)">批量停用({{ selectedRows.length }})</el-button>
|
|
|
</div>
|
|
|
<el-form :inline="true" class="mb-16">
|
|
|
<el-form-item label="状态"><el-select v-model="query.isEnabled" clearable><el-option label="启用" :value="1"/><el-option label="停用" :value="0"/></el-select></el-form-item>
|
|
|
<el-form-item><el-input v-model="query.keyword" placeholder="工号/姓名" clearable/></el-form-item>
|
|
|
<el-form-item><el-button type="primary" @click="loadData">查询</el-button><el-button @click="resetQuery">重置</el-button></el-form-item>
|
|
|
</el-form>
|
|
|
<el-table :data="tableData" border stripe v-loading="loading">
|
|
|
<el-table-column type="selection" width="50"/>
|
|
|
<el-table-column prop="code" label="工号"/>
|
|
|
<el-table-column prop="name" label="姓名"><template #default="{row}"><el-link type="primary" @click="goDetail(row.id)">{{row.name}}</el-link></template></el-table-column>
|
|
|
<el-table-column label="状态" align="center"><template #default="{row}"><el-tag :type="row.isEnabled?'success':'danger'" size="small">{{row.isEnabled?'启用':'停用'}}</el-tag></template></el-table-column>
|
|
|
<el-table-column prop="machineCount" label="绑定机床数" align="center"/>
|
|
|
<el-table-column prop="machineNames" label="绑定机床" show-overflow-tooltip/>
|
|
|
<el-table-column label="操作" width="120" align="center"><template #default="{row}">
|
|
|
<div style="white-space:nowrap">
|
|
|
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
|
|
<el-button link type="danger" @click="handleDelete(row)" :disabled="row.machineCount>0">删除</el-button>
|
|
|
</div>
|
|
|
</template></el-table-column>
|
|
|
</el-table>
|
|
|
<!-- 分页控件 -->
|
|
|
<div style="margin-top:12px;display:flex;justify-content:flex-end;align-items:center">
|
|
|
<el-pagination
|
|
|
:total="pagination.total"
|
|
|
:page-size="pagination.pageSize"
|
|
|
:page-sizes="[20, 50, 100]"
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
:current-page="pagination.currentPage"
|
|
|
@current-change="handlePageChange"
|
|
|
@size-change="handleSizeChange"
|
|
|
/>
|
|
|
</div>
|
|
|
<el-dialog v-model="dialogVisible" :title="editingId?'编辑工人':'新增工人'" width="700px" destroy-on-close>
|
|
|
<el-form :model="form" :rules="rules" ref="workerForm" label-width="100px">
|
|
|
<el-form-item label="工号" prop="code">
|
|
|
<el-input v-model="form.code" maxlength="50" @blur="()=>workerForm?.validateField('code')"/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="姓名" prop="name">
|
|
|
<el-input v-model="form.name" maxlength="50"/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="绑定机床">
|
|
|
<el-transfer
|
|
|
v-model="form.machineIds"
|
|
|
:data="availableMachines"
|
|
|
:titles="['全部机床', '已绑定']"
|
|
|
:props="{ key: 'id', label: 'label' }"
|
|
|
filterable
|
|
|
filter-placeholder="搜索机床"
|
|
|
>
|
|
|
<template #default="{ option }">
|
|
|
<span :class="['machine-transfer-item', getStatusClass(option)]">
|
|
|
<span class="status-dot"></span>
|
|
|
{{ option.name }}
|
|
|
<template v-if="option.deviceCode"> ({{ option.deviceCode }})</template>
|
|
|
<template v-if="option.workshopName"> - {{ option.workshopName }}</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"></span>停用</span>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button></template>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
|
import {ref, reactive,onMounted} from 'vue'
|
|
|
import {useRouter} from 'vue-router'
|
|
|
import {ElMessage,ElMessageBox} from 'element-plus'
|
|
|
import request from '@/utils/request'
|
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
|
import {useMockMode} from '@/composables/useMockMode'
|
|
|
import type { ApiResponse, Worker, Workshop, CollectAddress } from '@/types'
|
|
|
const router=useRouter();const{isMock}=useMockMode()
|
|
|
const loading=ref(false);const tableData=ref<Worker[]>([]);const selectedRows=ref<Worker[]>([])
|
|
|
// 分页信息
|
|
|
const pagination=ref<{ currentPage: number; pageSize: number; total: number }>({ currentPage: 1, pageSize: 20, total: 0 })
|
|
|
const dialogVisible=ref(false);const submitting=ref(false);const editingId=ref<number|null>(null)
|
|
|
interface MachineItem { id: number; name: string; label: string; deviceCode?: string; workshopName?: string; isOnline: number; isEnabled: number }
|
|
|
const availableMachines=ref<MachineItem[]>([])
|
|
|
const query=reactive({isEnabled:undefined as number | undefined,keyword:''})
|
|
|
const form=reactive({code:'',name:'',machineIds:[] as number[]})
|
|
|
const workerForm=ref<FormInstance | null>(null)
|
|
|
// 工号唯一性校验规则
|
|
|
const rules=reactive({
|
|
|
code: [] as any[],
|
|
|
})
|
|
|
function validateWorkerCode(rule: unknown, value: string, callback: (err?:Error)=>void){
|
|
|
const v = (value ?? '').toString().trim()
|
|
|
if(!v){ callback(new Error('工号为必填项')) ; return }
|
|
|
// 新增模式下检查重复
|
|
|
if(editingId.value === null){
|
|
|
const exists = tableData.value.some((r:any)=> r.code === v)
|
|
|
if(exists){ callback(new Error('该工号已存在')) ; return }
|
|
|
}
|
|
|
callback()
|
|
|
}
|
|
|
// 将校验函数挂载到 rules
|
|
|
rules.code = [{ validator: validateWorkerCode, trigger: 'blur' }]
|
|
|
function resetQuery(){query.isEnabled=undefined;query.keyword='';loadData()}
|
|
|
function goDetail(id:number){router.push((isMock.value?'/mock/worker/':'/worker/')+id)}
|
|
|
async function loadData(){loading.value=true;try{const ps=pagination.value.pageSize;const cp=pagination.value.currentPage;const r: ApiResponse<{ items: Worker[]; total?: number }> = await request.get('/admin/worker',{params:{...query,page:cp,pageSize:ps}});tableData.value=r.data?.items||[];pagination.value.total= r.data?.total ?? (r.data?.items?.length ?? 0)}finally{loading.value=false}}
|
|
|
function handleAdd(){editingId.value=null;Object.assign(form,{code:'',name:'',machineIds:[]});dialogVisible.value=true}
|
|
|
function handleEdit(row: Worker){editingId.value=row.id;Object.assign(form,{code:row.code,name:row.name,machineIds:row.machines?.map(m=>m.id)||[]});dialogVisible.value=true}
|
|
|
async function handleSubmit(){submitting.value=true;try{const ok = await (workerForm.value?.validate ? new Promise<boolean>((resolve)=>workerForm.value!.validate((valid:boolean)=>resolve(valid))) : Promise.resolve(true)); if(!ok){return} await request[editingId.value?'put':'post'](editingId.value?`/admin/worker/${editingId.value}`:'/admin/worker',{...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/worker/${row.id}`);ElMessage.success('已删除');loadData()}
|
|
|
async function batchStatus(isEnabled:number){await ElMessageBox.confirm('确定对选中的'+selectedRows.value.length+'项操作?','提示',{type:'warning'});for(const id of selectedRows.value.map((r:any)=>r.id)){await request.put(`/admin/worker/${id}/toggle`,{isEnabled})};ElMessage.success('操作成功');loadData()}
|
|
|
async function loadDrops(){try{const r: ApiResponse<{ items: MachineItem[] }> = await request.get('/admin/machine',{params:{pageSize:999}}); availableMachines.value = (r.data?.items ?? []).map(m => ({ id: m.id, name: m.name, label: m.name, deviceCode: m.deviceCode, workshopName: m.workshopName, isOnline: m.isOnline, isEnabled: m.isEnabled }))}catch{/* 接口不可用时保持为空,不影响其他功能 */}}
|
|
|
/** 根据机床在线/启用状态返回样式类 */
|
|
|
function getStatusClass(m: MachineItem): string {
|
|
|
if (!m.isEnabled) return 'status-disabled'
|
|
|
if (m.isOnline) return 'status-online'
|
|
|
return 'status-offline'
|
|
|
}
|
|
|
function handlePageChange(page:number){pagination.value.currentPage=page;loadData()}
|
|
|
function handleSizeChange(size:number){pagination.value.pageSize=size;pagination.value.currentPage=1;loadData()}
|
|
|
onMounted(()=>{loadData();loadDrops()})
|
|
|
</script>
|
|
|
<style scoped>
|
|
|
/* 穿梭框状态色点 */
|
|
|
.status-dot {
|
|
|
display: inline-block;
|
|
|
width: 8px;
|
|
|
height: 8px;
|
|
|
border-radius: 50%;
|
|
|
margin-right: 6px;
|
|
|
vertical-align: middle;
|
|
|
}
|
|
|
/* 穿梭框内:父子选择器(父元素带status-xxx,子元素.status-dot) */
|
|
|
.status-online .status-dot { background-color: #67c23a; }
|
|
|
.status-offline .status-dot { background-color: #c0c4cc; }
|
|
|
.status-disabled .status-dot { background-color: #f56c6c; }
|
|
|
/* 图例:class直接在.status-dot上 */
|
|
|
.status-dot.online { background-color: #67c23a; }
|
|
|
.status-dot.offline { background-color: #c0c4cc; }
|
|
|
.status-dot.disabled { 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>
|