You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
haoliang-net/frontend/src/views/worker/WorkerListPage.vue

158 lines
9.5 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>