<template>
|
<div class="app-container line-management">
|
<div class="config-wrap">
|
<div class="left">
|
<div class="header">
|
<div>产线管理</div>
|
<div>
|
<el-button size="small" type="primary" @click="addLine">新增产线</el-button>
|
</div>
|
</div>
|
<el-tree
|
:data="lineTree"
|
node-key="id"
|
:props="{ label: 'name' }"
|
@node-click="onNodeClick"
|
highlight-current
|
class="tree"
|
>
|
<template #default="{ data }">
|
<span>
|
<el-tag size="small" type="warning" effect="plain" style="margin-right:4px;">
|
产线
|
</el-tag>
|
{{ data.name }}
|
</span>
|
<span class="ops">
|
<el-button link size="small" type="primary" @click.stop="openRenameDialog(data)">重命名</el-button>
|
<el-button link size="small" type="danger" @click.stop="confirmRemoveNode(data)">删除</el-button>
|
</span>
|
</template>
|
</el-tree>
|
</div>
|
<div class="right">
|
<div v-if="currentLine" class="process-config">
|
<div class="title">
|
<span>工序管理:{{ currentLine.label }}</span>
|
</div>
|
<div class="q-toolbar">
|
<el-button size="small" type="primary" @click="openAddProcessDialog">新增工序</el-button>
|
</div>
|
<el-table :data="processList" border size="small" v-loading="processLoading">
|
<el-table-column label="工序名称" prop="name" />
|
<el-table-column label="操作" width="160" align="center">
|
<template #default="scope">
|
<el-button link type="primary" size="small" @click="openEditProcessDialog(scope.row)">编辑</el-button>
|
<el-button link type="danger" size="small" @click="onDeleteProcess(scope.row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<div class="pagination-container">
|
<el-pagination
|
v-model:current-page="processQueryParams.current"
|
v-model:page-size="processQueryParams.size"
|
:page-sizes="[10, 20, 50, 100]"
|
:layout="'total, sizes, prev, pager, next, jumper'"
|
:total="total"
|
@size-change="handleProcessSizeChange"
|
@current-change="handleProcessCurrentChange"
|
/>
|
</div>
|
</div>
|
<div v-else class="empty">请选择左侧产线进行工序配置</div>
|
</div>
|
</div>
|
<!-- 新增产线弹窗 -->
|
<el-dialog v-model="addDialogVisible" :title="addDialogTitle" width="420px">
|
<el-form :model="addForm" :rules="addRules" ref="addFormRef" label-width="80px">
|
<el-form-item label="产线名称" prop="name">
|
<el-input v-model="addForm.name" placeholder="请输入产线名称" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="onCancelAdd">取 消</el-button>
|
<el-button type="primary" @click="onConfirmAdd" :loading="addLoading">确 认</el-button>
|
</template>
|
</el-dialog>
|
<!-- 重命名弹窗 -->
|
<el-dialog v-model="renameDialogVisible" :title="renameDialogTitle" width="420px">
|
<el-form :model="renameForm" :rules="addRules" ref="renameFormRef" label-width="80px">
|
<el-form-item label="产线名称" prop="name">
|
<el-input v-model="renameForm.name" placeholder="请输入产线名称" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="onCancelRename">取 消</el-button>
|
<el-button type="primary" @click="onConfirmRename" :loading="renameLoading">确 认</el-button>
|
</template>
|
</el-dialog>
|
|
<!-- 新增/编辑工序弹窗 -->
|
<el-dialog v-model="addProcessDialogVisible" :title="addProcessDialogTitle" width="420px">
|
<el-form :model="addProcessForm" label-width="80px">
|
<el-form-item label="工序名称" prop="name">
|
<el-input v-model="addProcessForm.name" placeholder="请输入工序名称" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="onCancelAddProcess">取 消</el-button>
|
<el-button type="primary" :loading="addProcessSaving" @click="onConfirmAddProcess">保 存</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { getDeptPositionTree, addDeptPosition, updateDeptPosition, deleteDeptPosition, laborConfListPage } from '@/api/lavorissce/issue'
|
|
// 结构:产线列表
|
const lines = reactive([]) // [{id,name,processes:[{id,processName,processCode,standardTime,status}]}]
|
|
// 加载产线列表
|
async function loadLineTree() {
|
try {
|
const res = await getDeptPositionTree()
|
const data = res.data
|
lines.splice(0, lines.length, ...data)
|
} catch (e) {
|
// 静默失败,保留本地示例数据
|
}
|
}
|
|
const lineTree = computed(() => lines)
|
|
const currentLine = ref(null)
|
// 右侧:当前产线的工序列表
|
const processList = ref([])
|
const processLoading = ref(false)
|
|
// 工序分页参数
|
const processQueryParams = reactive({
|
current: 1,
|
size: 10
|
})
|
const total = ref(0)
|
|
// 加载工序列表
|
async function loadProcessList(lineId) {
|
if (!lineId) {
|
if (!currentLine.value) return
|
lineId = currentLine.value.id
|
}
|
|
try {
|
processLoading.value = true
|
const res = await laborConfListPage({
|
id: lineId,
|
current: processQueryParams.current,
|
size: processQueryParams.size
|
})
|
// 新接口返回的是树形结构,直接使用返回的数据
|
processList.value = res.data.records.map(it => ({
|
...it,
|
id: it.id,
|
name: it.name
|
}))
|
total.value = res.data.total
|
} finally {
|
processLoading.value = false
|
}
|
}
|
|
// 工序分页事件处理
|
function handleProcessSizeChange(val) {
|
processQueryParams.size = val
|
processQueryParams.current = 1
|
loadProcessList()
|
}
|
|
function handleProcessCurrentChange(val) {
|
processQueryParams.current = val
|
loadProcessList()
|
}
|
|
// 新增工序弹窗状态与逻辑(供模板使用)
|
const addProcessDialogVisible = ref(false)
|
const addProcessDialogTitle = ref('新增工序')
|
const addProcessSaving = ref(false)
|
const addProcessForm = reactive({ id: undefined, name: '' })
|
|
function openAddProcessDialog() {
|
if (!currentLine.value) return
|
addProcessDialogTitle.value = '新增工序'
|
addProcessForm.id = undefined
|
addProcessForm.name = ''
|
addProcessDialogVisible.value = true
|
}
|
|
function openEditProcessDialog(row) {
|
if (!currentLine.value || !row) return
|
addProcessDialogTitle.value = '编辑工序'
|
addProcessForm.id = row.id
|
addProcessForm.name = row.name
|
addProcessDialogVisible.value = true
|
}
|
|
function onCancelAddProcess() {
|
addProcessDialogVisible.value = false
|
}
|
|
async function onConfirmAddProcess() {
|
if (!currentLine.value.id) {
|
ElMessage.error('请先选择产线')
|
return
|
}
|
addProcessSaving.value = true
|
try {
|
if (addProcessForm.id) {
|
// 编辑工序
|
await updateDeptPosition({
|
id: addProcessForm.id,
|
name: addProcessForm.name,
|
type: 2 // 工序类型
|
})
|
} else {
|
// 新增工序
|
await addDeptPosition({
|
parentId: currentLine.value.id,
|
name: addProcessForm.name,
|
type: 2 // 工序类型
|
})
|
}
|
|
// 重新加载工序列表
|
await loadProcessList()
|
|
addProcessDialogVisible.value = false
|
ElMessage.success('保存成功')
|
} catch (e) {
|
ElMessage.error((e && (e.msg || e.message)) || '保存失败')
|
} finally {
|
addProcessSaving.value = false
|
}
|
}
|
|
async function onDeleteProcess(row) {
|
if (!currentLine.value || !row) return
|
try {
|
await ElMessageBox.confirm('确定删除该工序吗?', '提示', { type: 'warning' })
|
} catch {
|
return
|
}
|
try {
|
await deleteDeptPosition([row.id])
|
// 重新加载工序列表
|
await loadProcessList()
|
ElMessage.success('删除成功')
|
} catch (e) {
|
ElMessage.error((e && (e.msg || e.message)) || '删除失败')
|
}
|
}
|
|
// 新增产线弹窗状态
|
const addDialogVisible = ref(false)
|
const addDialogTitle = ref('新增产线')
|
const addLoading = ref(false)
|
const addFormRef = ref()
|
const addForm = reactive({ name: '', type: 1, parentId: undefined })
|
const addRules = { name: [{ required: true, message: '请输入产线名称', trigger: 'blur' }] }
|
|
// 重命名产线弹窗状态
|
const renameDialogVisible = ref(false)
|
const renameLoading = ref(false)
|
const renameFormRef = ref()
|
const renameForm = reactive({ id: undefined, type: 1, name: '' })
|
const renameDialogTitle = ref('重命名产线')
|
|
function newLine() {
|
return {
|
id: Date.now(),
|
label: '新产线',
|
hrPositionId: undefined,
|
processes: []
|
}
|
}
|
|
async function addLine() {
|
openAddDialog()
|
}
|
|
function openAddDialog() {
|
addForm.name = ''
|
addForm.type = 1
|
addForm.parentId = undefined
|
addDialogTitle.value = '新增产线'
|
addDialogVisible.value = true
|
}
|
|
function onCancelAdd() {
|
addDialogVisible.value = false
|
}
|
|
async function onConfirmAdd() {
|
addFormRef.value?.validate(async (valid) => {
|
if (!valid) return
|
try {
|
addLoading.value = true
|
const payload = { name: addForm.name, type: addForm.type, parentId: 0 }
|
await addDeptPosition(payload)
|
addDialogVisible.value = false
|
await loadLineTree()
|
} finally {
|
addLoading.value = false
|
}
|
})
|
}
|
|
function onNodeClick(node) {
|
console.log(node);
|
|
currentLine.value = node
|
// 选择产线时,加载工序列表
|
loadProcessList()
|
}
|
|
function openRenameDialog(node) {
|
renameForm.id = node.id
|
renameForm.type = 1
|
renameForm.name = node.name
|
renameDialogTitle.value = '重命名产线'
|
renameDialogVisible.value = true
|
}
|
|
function onCancelRename() {
|
renameDialogVisible.value = false
|
}
|
|
async function onConfirmRename() {
|
renameFormRef.value?.validate(async (valid) => {
|
if (!valid) return
|
try {
|
renameLoading.value = true
|
await updateDeptPosition({ id: renameForm.id, name: renameForm.name, type: renameForm.type })
|
renameDialogVisible.value = false
|
await loadLineTree()
|
} finally {
|
renameLoading.value = false
|
}
|
})
|
}
|
|
async function confirmRemoveNode(node) {
|
const id = node.id
|
// 简单确认
|
try {
|
await deleteDeptPosition([id])
|
await loadLineTree()
|
} catch (e) {
|
// ignore errors
|
}
|
}
|
|
|
|
onMounted(() => {
|
loadLineTree()
|
})
|
</script>
|
|
<style scoped>
|
.labor-issue {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
}
|
.config-wrap {
|
display: flex;
|
gap: 16px;
|
align-items: stretch;
|
}
|
.left, .right {
|
background: #fff;
|
border: 1px solid var(--el-border-color, #ebeef5);
|
border-radius: 8px;
|
box-shadow: var(--el-box-shadow-light, 0 2px 12px 0 rgba(0,0,0,.1));
|
}
|
.left { width: 340px; padding: 12px; }
|
.right { flex: 1; padding: 14px; }
|
.header {
|
display: flex;
|
align-items: flex-start;
|
margin-bottom: 10px;
|
flex-direction: column;
|
}
|
.header :deep(.el-button+.el-button) { margin-left: 8px; }
|
.tree {
|
max-height: calc(100vh - 300px);
|
overflow: auto;
|
padding: 6px;
|
border-radius: 6px;
|
background: #fafafa;
|
}
|
.ops { margin-left: 8px; opacity: 0.6; transition: opacity .2s; }
|
:deep(.el-tree-node__content):hover .ops { opacity: 1; }
|
|
.q-toolbar { margin-bottom: 10px; }
|
.empty { color: #999; padding: 48px; text-align: center; }
|
|
.summary-wrap { display: flex; flex-direction: column; gap: 16px; }
|
.people, .summary {
|
background: #fff;
|
border: 1px solid var(--el-border-color, #ebeef5);
|
border-radius: 8px;
|
padding: 12px;
|
box-shadow: var(--el-box-shadow-light, 0 2px 12px 0 rgba(0,0,0,.06));
|
}
|
.summary .header {
|
font-weight: 600;
|
margin-bottom: 12px;
|
}
|
</style>
|