<template>
|
<el-dialog
|
:title="title"
|
v-model="visible"
|
width="1000px"
|
append-to-body
|
@close="handleClose"
|
>
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="0">
|
<!-- 顶部基础信息 -->
|
<div class="base-info-row">
|
<div class="info-item">
|
<span class="item-label required">名称</span>
|
<el-input
|
v-model="form.name"
|
placeholder="请输入名称"
|
maxlength="10"
|
show-word-limit
|
style="width: 220px"
|
/>
|
</div>
|
<div class="info-item">
|
<span class="item-label">备注</span>
|
<el-input
|
v-model="form.description"
|
placeholder="请输入备注"
|
maxlength="20"
|
show-word-limit
|
style="width: 220px"
|
/>
|
</div>
|
<div class="info-item">
|
<span class="item-label">附件</span>
|
<el-upload
|
action="#"
|
:auto-upload="false"
|
:show-file-list="false"
|
@change="handleFileChange"
|
>
|
<el-button type="primary">上传附件</el-button>
|
</el-upload>
|
</div>
|
</div>
|
|
<!-- 步骤配置表格 -->
|
<div class="step-table-container">
|
<el-table :data="form.savePlanNodeList" border style="width: 100%">
|
<el-table-column label="步骤" width="80" align="center">
|
<template #default="scope">
|
{{ scope.$index + 1 }}
|
</template>
|
</el-table-column>
|
|
<el-table-column label="阶段名称" min-width="150">
|
<template #header>
|
<span class="required-star">*</span> 阶段名称
|
</template>
|
<template #default="scope">
|
<el-form-item
|
:prop="'savePlanNodeList.' + scope.$index + '.name'"
|
:rules="[{ required: true, message: '请输入阶段名称', trigger: 'blur' }]"
|
>
|
<el-input v-model="scope.row.name" placeholder="请输入" />
|
</el-form-item>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="负责人" width="180">
|
<template #header>
|
<span class="required-star">*</span> 负责人
|
</template>
|
<template #default="scope">
|
<el-form-item
|
:prop="'savePlanNodeList.' + scope.$index + '.leaderId'"
|
:rules="[{ required: true, message: '请选择负责人', trigger: 'change' }]"
|
>
|
<el-select
|
v-model="scope.row.leaderId"
|
placeholder="测试"
|
@change="(val) => handleLeaderChange(val, scope.row)"
|
>
|
<el-option
|
v-for="item in userOptions"
|
:key="item.userId"
|
:label="item.nickName"
|
:value="item.userId"
|
/>
|
</el-select>
|
</el-form-item>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="预计工期 (天)" width="150">
|
<template #header>
|
预计工期 (天)
|
<el-tooltip content="完成该阶段预计需要的天数" placement="top">
|
<el-icon class="info-icon"><QuestionFilled /></el-icon>
|
</el-tooltip>
|
</template>
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.estimatedDuration"
|
:min="0"
|
controls-position="right"
|
style="width: 100%"
|
/>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="工时单价" width="120">
|
<template #default="scope">
|
<el-input v-model="scope.row.hourlyRate" placeholder="请输入" />
|
</template>
|
</el-table-column>
|
|
<el-table-column label="作业内容" min-width="150">
|
<template #default="scope">
|
<el-input v-model="scope.row.workContent" placeholder="请输入" />
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" width="180" align="center">
|
<template #default="scope">
|
<el-button
|
link
|
type="primary"
|
:disabled="scope.$index === 0"
|
@click="moveStep(scope.$index, -1)"
|
>上移</el-button>
|
<el-button
|
link
|
type="primary"
|
:disabled="scope.$index === form.savePlanNodeList.length - 1"
|
@click="moveStep(scope.$index, 1)"
|
>下移</el-button>
|
<el-button
|
link
|
type="danger"
|
@click="removeStep(scope.$index)"
|
>删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<div class="add-row-btn" @click="addStep">
|
<el-icon><Plus /></el-icon> 新增一行
|
</div>
|
</div>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="visible = false">取消</el-button>
|
<el-button type="primary" @click="submitForm">提交</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { ref, watch, onMounted } from 'vue';
|
import { Plus, QuestionFilled } from '@element-plus/icons-vue';
|
import { userListNoPageByTenantId } from '@/api/system/user';
|
import { ElMessage } from 'element-plus';
|
|
const props = defineProps({
|
modelValue: Boolean,
|
title: String,
|
data: Object
|
});
|
|
const emit = defineEmits(['update:modelValue', 'submit']);
|
|
const visible = ref(false);
|
const formRef = ref(null);
|
const userOptions = ref([]);
|
|
const form = ref({
|
id: undefined,
|
name: '',
|
description: '',
|
attachmentIds: [],
|
savePlanNodeList: []
|
});
|
|
const rules = {
|
name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
|
};
|
|
// 监听弹窗显示/隐藏
|
watch(() => props.modelValue, (val) => {
|
visible.value = val;
|
if (val) {
|
if (props.data) {
|
// 编辑模式
|
form.value = JSON.parse(JSON.stringify(props.data));
|
if (!form.value.savePlanNodeList || form.value.savePlanNodeList.length === 0) {
|
form.value.savePlanNodeList = [createDefaultNode()];
|
}
|
} else {
|
// 新增模式
|
resetForm();
|
}
|
}
|
});
|
|
watch(visible, (val) => {
|
emit('update:modelValue', val);
|
});
|
|
/** 创建默认节点对象 */
|
function createDefaultNode() {
|
return {
|
name: '',
|
leaderId: null,
|
leaderName: null,
|
estimatedDuration: null,
|
hourlyRate: null,
|
workContent: null
|
};
|
}
|
|
/** 重置表单 */
|
function resetForm() {
|
form.value = {
|
id: undefined,
|
name: '',
|
description: '',
|
attachmentIds: [],
|
savePlanNodeList: [createDefaultNode()]
|
};
|
if (formRef.value) {
|
formRef.value.resetFields();
|
}
|
}
|
|
/** 获取用户列表 */
|
async function getUserList() {
|
try {
|
const res = await userListNoPageByTenantId();
|
if (res.code === 200) {
|
userOptions.value = res.data || [];
|
}
|
} catch (error) {
|
console.error('获取用户列表失败:', error);
|
}
|
}
|
|
/** 处理负责人变化 */
|
function handleLeaderChange(val, row) {
|
const user = userOptions.value.find(u => u.userId === val);
|
if (user) {
|
row.leaderName = user.nickName;
|
}
|
}
|
|
/** 处理文件变化 */
|
function handleFileChange(file) {
|
// 这里实现文件上传逻辑,获取 attachmentId
|
ElMessage.info('正在上传: ' + file.name);
|
// 模拟上传成功
|
// form.value.attachmentIds.push(newId);
|
}
|
|
/** 添加步骤 */
|
function addStep() {
|
form.value.savePlanNodeList.push(createDefaultNode());
|
}
|
|
/** 移除步骤 */
|
function removeStep(index) {
|
if (form.value.savePlanNodeList.length <= 1) {
|
ElMessage.warning('至少保留一个步骤');
|
return;
|
}
|
form.value.savePlanNodeList.splice(index, 1);
|
}
|
|
/** 移动步骤 */
|
function moveStep(index, direction) {
|
const targetIndex = index + direction;
|
if (targetIndex < 0 || targetIndex >= form.value.savePlanNodeList.length) return;
|
|
const list = form.value.savePlanNodeList;
|
const temp = list[index];
|
list[index] = list[targetIndex];
|
list[targetIndex] = temp;
|
}
|
|
/** 提交表单 */
|
async function submitForm() {
|
if (!formRef.value) return;
|
|
try {
|
const valid = await formRef.value.validate();
|
if (valid) {
|
emit('submit', form.value);
|
}
|
} catch (error) {
|
console.error('表单验证失败:', error);
|
}
|
}
|
|
/** 关闭弹窗 */
|
function handleClose() {
|
resetForm();
|
}
|
|
onMounted(() => {
|
getUserList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.base-info-row {
|
display: flex;
|
gap: 40px;
|
margin-bottom: 25px;
|
padding: 0 10px;
|
|
.info-item {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
|
.item-label {
|
font-size: 14px;
|
color: #606266;
|
white-space: nowrap;
|
|
&.required::before {
|
content: '*';
|
color: #f56c6c;
|
margin-right: 4px;
|
}
|
}
|
}
|
}
|
|
.step-table-container {
|
padding: 0 10px;
|
|
:deep(.el-form-item) {
|
margin-bottom: 0;
|
}
|
|
.required-star {
|
color: #f56c6c;
|
margin-right: 4px;
|
}
|
|
.info-icon {
|
font-size: 14px;
|
color: #909399;
|
margin-left: 4px;
|
cursor: pointer;
|
}
|
|
.add-row-btn {
|
margin-top: 15px;
|
height: 40px;
|
border: 1px dashed #dcdfe6;
|
border-radius: 4px;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
color: #409eff;
|
cursor: pointer;
|
font-size: 14px;
|
transition: all 0.3s;
|
|
&:hover {
|
border-color: #409eff;
|
background-color: #f0f7ff;
|
}
|
}
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 15px;
|
padding-top: 10px;
|
}
|
</style>
|