Files
art-design/src/views/admin/approval/approval_edit.vue
T
2025-09-15 17:14:38 +08:00

1374 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="approval-flow-container">
<div class="main-content">
<!-- 左侧基本信息 -->
<div class="left-panel">
<h3 class="panel-title">基本信息</h3>
<el-form :model="form" label-width="80px">
<el-form-item label="名称">
<el-input v-model="form.name" placeholder="请输入流程名称" />
</el-form-item>
<el-form-item label="业务类型">
<el-input v-model="form.type" placeholder="请输入业务类型" />
</el-form-item>
<el-form-item label="超时时间">
<ElInput v-model="form.timeout_hours" placeholder="请输入超时时间">
<template #suffix>
<span>h</span>
</template>
</ElInput>
</el-form-item>
<el-form-item label="超时动作">
<el-select v-model="form.timeout_action" placeholder="请选择超时动作">
<el-option label="请选择" value="" />
<el-option label="全部同意" value="all_agree" />
<el-option label="全部拒绝" value="all_reject" />
</el-select>
</el-form-item>
<el-form-item label="是否启用">
<el-switch v-model="form.is_active" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
</div>
<!-- 中间审批流程 -->
<div class="middle-panel">
<h3 class="panel-title">审批流</h3>
<div class="flow-diagram">
<template v-for="(node, index) in form.nodes" :key="node.id">
<!-- 节点按钮 -->
<div class="node-container">
<el-button
type="primary"
icon="Plus"
@click="selectNode(node)"
class="node-btn"
:class="{
selected: selectedNodeId === node.id,
disabled: index === 0
}"
:disabled="index === 0"
>
</el-button>
<div class="node-info" @click="selectNode(node)">
<div class="node-title">{{ node.title }}</div>
<div class="node-subtitle">{{ node.subtitle }}</div>
</div>
<!-- 删除按钮 -->
<i
v-if="form.nodes.length >= 3 && index > 0"
class="iconfont-sys danger-color"
@click="removeNode(node.id)"
>&#xe6d4;</i
>
</div>
<!-- 连接线 -->
<div v-if="index < form.nodes.length" class="connection-line">
<div class="dotted-line"></div>
<el-button
type="primary"
icon="Plus"
@click="addNodeAfter(node.id)"
class="add-connection-btn"
size="small"
circle
/>
</div>
</template>
</div>
</div>
<!-- 右侧审批人配置 -->
<div class="right-panel">
<div class="panel-header">
<h3 class="panel-title">审批人</h3>
</div>
<!-- 提示信息 -->
<div v-if="!selectedNode || selectedNodeId === form.nodes[0]?.id" class="no-selection">
<div class="no-selection-content">
<i class="el-icon-info"></i>
<p v-if="!selectedNode">请选择一个节点进行配置</p>
<p v-else>申请人节点为默认节点,不可配置</p>
</div>
</div>
<div v-if="selectedNode && selectedNodeId !== form.nodes[0]?.id" class="approver-config">
<!-- 节点基本信息 -->
<div class="config-section">
<h4>节点信息</h4>
<el-form :model="selectedNode" label-width="60px" size="small">
<el-form-item label="名称">
<el-input
v-model="selectedNode.title"
placeholder="请输入节点名称"
@blur="updateNodeLabel"
/>
</el-form-item>
<el-form-item label="类型">
<el-select
v-model="selectedNode.type"
@change="onNodeTypeChange"
placeholder="请选择节点类型"
>
<el-option
v-for="item in approvalType"
:key="item.item_value"
:label="item.item_label"
:value="item.item_value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批条件">
<el-input
v-model="selectedNode.condition"
placeholder="请输入审批条件)"
type="textarea"
:rows="2"
/>
</el-form-item>
<el-form-item label="是否必须">
<el-switch
v-model="selectedNode.required"
active-text="必须"
inactive-text="可选"
/>
</el-form-item>
</el-form>
</div>
<div class="config-section">
<h4>审批人</h4>
<el-checkbox-group v-model="selectedNode.approverUsers">
<!-- <el-checkbox label="上级领导">上级领导</el-checkbox> -->
</el-checkbox-group>
<div class="approver-groups">
<div
v-for="(group, groupIndex) in selectedNode.approverGroups"
:key="groupIndex"
class="approver-group"
>
<div class="group-header">
<span class="group-title">审批组 {{ groupIndex + 1 }}</span>
<el-button
v-if="selectedNode.approverGroups.length > 1"
type="danger"
size="small"
icon="Delete"
@click="removeApproverGroup(groupIndex)"
circle
/>
</div>
<div class="group-content">
<el-form-item label="类型" size="small">
<el-select
v-model="group.type"
placeholder="请选择审批类型"
@change="onGroupTypeChange(group)"
>
<el-option
v-for="item in approvalRole"
:key="item.item_value"
:label="item.item_label"
:value="item.item_value"
/>
</el-select>
</el-form-item>
<el-form-item label="选项" size="small">
<el-select
v-model="group.users"
multiple
placeholder="请选择"
:loading="group.loading"
ollapse-tags
filterable
:disabled="group.type === 'initiator_leader'"
>
<el-option
v-for="opt in group.options"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</div>
</div>
<el-button
type="primary"
size="small"
icon="Plus"
@click="addApproverGroup"
class="add-group-btn"
>
添加审批组
</el-button>
</div>
</div>
<div class="config-section">
<div class="cc-header">
<h4>是否抄送</h4>
<el-switch v-model="selectedNode.isCC" />
</div>
<div v-if="selectedNode.isCC" class="cc-config">
<h4>抄送人</h4>
<el-checkbox-group v-model="selectedNode.ccUsers">
<!-- <el-checkbox label="上级领导">上级领导</el-checkbox> -->
</el-checkbox-group>
<div class="cc-groups">
<div
v-for="(group, groupIndex) in selectedNode.ccGroups"
:key="groupIndex"
class="cc-group"
>
<div class="group-header">
<span class="group-title">抄送组 {{ groupIndex + 1 }}</span>
<el-button
v-if="selectedNode.ccGroups.length > 1"
type="danger"
size="small"
icon="Delete"
@click="removeCCGroup(groupIndex)"
>
</el-button>
</div>
<div class="group-content">
<el-form-item label="类型" size="small">
<el-select
v-model="group.type"
placeholder="请选择类型"
style="width: 100%"
@change="onGroupTypeChange(group)"
>
<el-option
v-for="item in approvalRole"
:key="item.item_value"
:label="item.item_label"
:value="item.item_value"
/>
</el-select>
</el-form-item>
<el-form-item label="选项" size="small">
<el-select
v-model="group.users"
multiple
placeholder="请选择"
:loading="group.loading"
:disabled="group.type === 'initiator_leader'"
>
<el-option
v-for="opt in group.options"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</div>
</div>
<el-button
type="primary"
size="small"
icon="Plus"
@click="addCCGroup"
class="add-group-btn"
>
添加抄送组
</el-button>
</div>
</div>
</div>
</div>
<!-- <div v-else class="no-selection">
<p>请选择一个节点进行配置</p>
</div> -->
</div>
</div>
<!-- 底部操作按钮 -->
<div class="bottom-actions">
<el-button @click="cancelFlow">取消</el-button>
<el-button type="primary" @click="saveFlow">保存</el-button>
</div>
<!-- 节点编辑弹框 -->
<el-dialog
v-model="editDialogVisible"
:title="editingNode.id === null ? '添加新节点' : '编辑节点名称'"
width="400px"
:close-on-click-modal="false"
>
<el-form :model="editingNode" label-width="80px">
<el-form-item label="节点名称">
<el-input v-model="editingNode.label" placeholder="请输入节点名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" @click="saveNode">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { commonService } from '@/api/commonApi'
import { manageService } from '@/api/adminApi'
defineOptions({ name: 'AdminApprovalEdit' })
//审批类型 AWF_STEP_TYPE
const approvalType = ref([])
//角色类型
const approvalRole = ref([])
const loadDictOptions = async () => {
const response = await commonService.getDictSelect({
scope: 'awf_step_type,awf_approvers_type',
scope_type: '1'
})
approvalType.value = response.awf_step_type
approvalRole.value = response.awf_approvers_type
}
const route = useRoute()
const router = useRouter()
onMounted(async () => {
await loadDictOptions()
await loadDetail()
})
// 审批流程数据
const form = reactive({
id: '',
name: '',
type: '',
timeout_hours: '',
timeout_action: '',
is_active: 1,
nodes: [
{
id: 1,
label: '申请人',
title: '申请人',
subtitle: '本人',
type: '',
condition: '',
required: true,
approverGroups: [
{
type: '',
users: [],
loading: false,
options: []
}
],
approvers: [],
businessApprovers: [],
approverUsers: [],
ccGroups: [
{
type: '',
users: [],
loading: false,
options: []
}
],
ccUsers: [],
isCC: false
},
{
id: 2,
label: '审批人',
title: '审批人',
subtitle: '上级',
type: '',
condition: '',
required: true,
approverGroups: [
{
type: '',
users: [],
loading: false,
options: []
}
],
approvers: [],
businessApprovers: [],
approverUsers: [],
ccGroups: [
{
type: '',
users: [],
loading: false,
options: []
}
],
ccUsers: [],
isCC: false
}
]
})
// 编辑弹框状态
const editDialogVisible = ref(false)
const editingNode = reactive({
id: null,
label: '',
afterNodeId: null // 用于记录在哪个节点后添加新节点
})
// 选中的节点
const selectedNodeId = ref(null)
const selectedNode = ref(null)
// 业务部用户数据(如需部门静态项,可在此填充)
// const businessUsers = ref([])
// 选项缓存,避免重复请求
const optionsCache = reactive({ role: [], user: [], department: [] })
// 懒加载并缓存不同类型的选项
const ensureOptionsLoaded = async (group) => {
const type = group.type
if (!type || type === 'initiator_leader') {
group.options = []
return
}
group.loading = true
try {
if (type === 'role') {
if (!optionsCache.role || optionsCache.role.length === 0) {
const response = await commonService.getRoleSelect()
optionsCache.role = response || []
}
console.log('optionsCache.role', optionsCache.role)
group.options = optionsCache.role
} else if (type === 'user') {
if (!optionsCache.user || optionsCache.user.length === 0) {
const response = await commonService.getUserSelect()
optionsCache.user = response || []
}
group.options = optionsCache.user
} else {
group.options = []
}
} finally {
group.loading = false
}
}
// 节点ID计数器
let nodeIdCounter = 3
// 选择节点
const selectNode = (node) => {
// 第一个节点(申请人)不可选择
const nodeIndex = form.nodes.findIndex((n) => n.id === node.id)
if (nodeIndex === 0) {
return
}
selectedNodeId.value = node.id
selectedNode.value = node
}
// 更新节点标签(当名称改变时)
const updateNodeLabel = () => {
if (selectedNode.value) {
selectedNode.value.label = selectedNode.value.title
}
}
// 节点类型改变
const onNodeTypeChange = (type) => {
if (selectedNode.value) {
// 根据类型设置默认值
if (type === 'applicant') {
selectedNode.value.subtitle = '本人'
} else if (type === 'approver') {
selectedNode.value.subtitle = '请选择'
} else if (type === 'cc') {
selectedNode.value.subtitle = '请选择'
selectedNode.value.isCC = true
}
}
}
// 添加审批组
const addApproverGroup = () => {
if (selectedNode.value) {
selectedNode.value.approverGroups.push({
type: '',
users: [],
loading: false,
options: optionsCache.department
})
}
}
// 删除审批组
const removeApproverGroup = (groupIndex) => {
if (selectedNode.value && selectedNode.value.approverGroups.length > 1) {
selectedNode.value.approverGroups.splice(groupIndex, 1)
}
}
// 添加抄送组
const addCCGroup = () => {
if (selectedNode.value) {
selectedNode.value.ccGroups.push({
type: '',
users: [],
loading: false,
options: optionsCache.department
})
}
}
// 删除抄送组
const removeCCGroup = (groupIndex) => {
if (selectedNode.value && selectedNode.value.ccGroups.length > 1) {
selectedNode.value.ccGroups.splice(groupIndex, 1)
}
}
// (已由 ensureOptionsLoaded 替代)
// 类型变化时,清空选项(如发起人上级则禁用)
const onGroupTypeChange = async (group) => {
group.users = []
group.options = []
await ensureOptionsLoaded(group)
}
// 加载详情并回显
const loadDetail = async () => {
const id = route.params.id || route.query.id
let data = {}
if (!id) return
await manageService.stockapprovaldetail({ id }).then((res) => {
data = res
})
console.log('data', data)
if (!data) return
form.id = data.id
form.name = data.name || ''
form.type = data.type || ''
form.timeout_hours = data.timeout_hours ?? ''
form.timeout_action = data.timeout_action || ''
form.is_active = data.is_active ? 1 : 0
data.steps = JSON.parse(data.steps)
console.log('data', data.steps)
// 重建节点:第一个默认申请人 + 每个步骤一个节点
const nodes = [form.nodes[0]]
if (Array.isArray(data.steps)) {
for (const step of data.steps) {
const node = {
id: ++nodeIdCounter,
label: step.name || '审批节点',
title: step.name || '审批节点',
subtitle: '上级',
type: step.type || 'any',
condition: step.condition || '',
required: !!step.required,
approverGroups: [],
approverUsers: [],
ccGroups: [],
ccUsers: [],
isCC: Array.isArray(step.cc) && step.cc.length > 0
}
// 审批组
if (Array.isArray(step.approvers)) {
for (const ap of step.approvers) {
const group = { type: ap.type || '', users: [], loading: false, options: [] }
await ensureOptionsLoaded(group)
if (ap.type === 'initiator_leader') {
group.users = []
} else if (ap.value) {
group.users = String(ap.value)
.split(',')
.map((v) => Number((v || '').trim()))
.filter((n) => !Number.isNaN(n))
}
node.approverGroups.push(group)
}
} else {
// 至少保留一个空组方便编辑
const group = { type: '', users: [], loading: false, options: [] }
await ensureOptionsLoaded(group)
node.approverGroups.push(group)
}
// 抄送组
if (Array.isArray(step.cc)) {
for (const c of step.cc) {
const group = { type: c.type || '', users: [], loading: false, options: [] }
await ensureOptionsLoaded(group)
if (c.type === 'initiator_leader') {
group.users = []
} else if (c.value) {
group.users = String(c.value)
.split(',')
.map((v) => Number((v || '').trim()))
.filter((n) => !Number.isNaN(n))
}
node.ccGroups.push(group)
}
} else {
const group = { type: '', users: [], loading: false, options: [] }
await ensureOptionsLoaded(group)
node.ccGroups.push(group)
}
nodes.push(node)
}
}
form.nodes = nodes
}
// 保存节点
const saveNode = () => {
if (editingNode.id === null) {
// 添加新节点
if (editingNode.afterNodeId) {
const afterIndex = form.nodes.findIndex((n) => n.id === editingNode.afterNodeId)
if (afterIndex !== -1) {
const newNode = {
id: ++nodeIdCounter,
label: editingNode.label,
title: editingNode.label,
subtitle: '请选择',
type: '',
condition: '',
required: true,
approverGroups: [
{
type: '',
users: [],
loading: false
}
],
approvers: [],
businessApprovers: [],
ccUsers: [],
businessCCUsers: [],
isCC: false
}
form.nodes.splice(afterIndex + 1, 0, newNode)
}
}
} else {
// 编辑现有节点
const nodeIndex = form.nodes.findIndex((n) => n.id === editingNode.id)
if (nodeIndex !== -1) {
form.nodes[nodeIndex].label = editingNode.label
form.nodes[nodeIndex].title = editingNode.label
}
}
// 重置编辑状态
editingNode.id = null
editingNode.label = ''
editingNode.afterNodeId = null
editDialogVisible.value = false
}
// 在指定节点后添加新节点
const addNodeAfter = (afterNodeId) => {
// 先打开编辑弹框,让用户输入节点名称
editingNode.id = null // 设置为null表示这是新节点
editingNode.label = ''
editingNode.afterNodeId = afterNodeId
editDialogVisible.value = true
}
// 取消编辑
const cancelEdit = () => {
// 重置编辑状态
editingNode.id = null
editingNode.label = ''
editingNode.afterNodeId = null
editDialogVisible.value = false
}
// 删除节点
const removeNode = (nodeId) => {
const nodeIndex = form.nodes.findIndex((n) => n.id === nodeId)
if (nodeIndex !== -1 && nodeIndex > 0) {
form.nodes.splice(nodeIndex, 1)
}
}
// 保存流程 -> 组装为后端需要的结构
const saveFlow = async () => {
const timeout = Number(form.timeout_hours || 0)
if (timeout > 0 && !form.timeout_action) {
ElMessage.error('当超时时间大于0时,必须选择超时动作')
return
}
const payload = {
id: form.id,
name: form.name,
type: form.type,
timeout_hours: timeout,
timeout_action: form.timeout_action,
is_active: !!form.is_active,
steps: []
}
const steps = []
form.nodes.forEach((node, idx) => {
// 跳过第一个默认的申请人节点
if (idx === 0) return
const approvers = []
// 组审批
if (Array.isArray(node.approverGroups)) {
node.approverGroups.forEach((group) => {
if (group && group.type === 'initiator_leader') {
approvers.push({ type: 'initiator_leader' })
} else if (group && group.type && Array.isArray(group.users) && group.users.length > 0) {
approvers.push({ type: group.type, value: group.users.join(',') })
}
})
}
// // 单选“上级领导”
// if (Array.isArray(node.approverUsers) && node.approverUsers.includes('上级领导')) {
// approvers.push({ type: 'initiator_leader' })
// }
const cc = []
if (node.isCC && Array.isArray(node.ccGroups)) {
node.ccGroups.forEach((group) => {
if (group && group.type === 'initiator_leader') {
cc.push({ type: 'initiator_leader' })
} else if (group && group.type && Array.isArray(group.users) && group.users.length > 0) {
cc.push({ type: group.type, value: group.users.join(',') })
}
})
}
// if (node.isCC && Array.isArray(node.ccUsers) && node.ccUsers.includes('上级领导')) {
// cc.push({ type: 'initiator_leader' })
// }
steps.push({
name: node.title || node.label || '',
type: node.type || 'any',
approvers,
cc,
condition: node.condition || '',
required: !!node.required
})
})
payload.steps = steps
const { message } = await manageService.stockapprovalupdate(payload)
ElMessage.success(message)
console.log('保存的流程数据:', payload)
setTimeout(() => {
router.back()
}, 300)
}
// 取消流程
const cancelFlow = () => {
console.log('取消流程编辑')
}
</script>
<style scoped>
.approval-flow-container {
display: flex;
flex-direction: column;
height: 88vh;
/* padding: 20px; */
/* gap: 20px; */
}
.main-content {
display: flex;
flex: 1;
gap: 20px;
}
.left-panel,
.middle-panel,
.right-panel {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: 75vh;
}
.left-panel {
width: 300px;
flex-shrink: 0;
}
.middle-panel {
flex: 1;
min-width: 400px;
display: flex;
flex-direction: column;
}
.right-panel {
width: 350px;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.panel-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.panel-header .panel-title {
margin-bottom: 0;
border-bottom: none;
}
.unit {
margin-left: 8px;
color: #909399;
}
.flow-diagram {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 0;
flex: 1;
overflow-y: auto;
padding: 20px 10px;
max-height: calc(100vh - 200px);
}
.node-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 15px;
position: relative;
margin: 10px 0;
}
.node-btn {
width: 50px;
height: 50px !important;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
transition: all 0.3s ease;
}
.node-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
}
.node-btn.selected {
background-color: #67c23a;
border-color: #67c23a;
}
.node-btn.selected:hover {
background-color: #85ce61;
border-color: #85ce61;
}
.node-btn.disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #6ca3f5;
border-color: #6ca3f5;
color: #fff;
box-shadow: none;
}
.node-btn.disabled:hover {
background-color: #f5f7fa;
border-color: #dcdfe6;
color: #909399;
transform: none;
box-shadow: none;
}
.cc-node {
background-color: #67c23a;
border-color: #67c23a;
}
.cc-node:hover {
background-color: #85ce61;
border-color: #85ce61;
}
.node-info {
display: flex;
flex-direction: column;
gap: 4px;
/* min-width: 120px; */
text-align: left;
}
.node-title {
font-size: 16px;
font-weight: 600;
color: #303133;
line-height: 1.2;
}
.node-subtitle {
font-size: 14px;
color: #909399;
line-height: 1.2;
}
.danger-color {
position: absolute;
right: -30px;
top: 50%;
transform: translateY(-50%);
color: #f56c6c;
}
.connection-line {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
margin: 5px 0;
}
.dotted-line {
width: 2px;
height: 80px;
background: repeating-linear-gradient(
to bottom,
#409eff 0px,
#409eff 4px,
transparent 4px,
transparent 8px
);
margin: 10px 0;
}
.add-connection-btn {
width: 24px;
height: 24px;
padding: 0;
background-color: #409eff;
border-color: #409eff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
.add-connection-btn:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
.approver-config {
margin-top: 20px;
flex: 1;
overflow-y: auto;
padding-right: 10px;
}
/* 滚动条样式 */
.flow-diagram::-webkit-scrollbar,
.approver-config::-webkit-scrollbar {
width: 6px;
}
.flow-diagram::-webkit-scrollbar-track,
.approver-config::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.flow-diagram::-webkit-scrollbar-thumb,
.approver-config::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.flow-diagram::-webkit-scrollbar-thumb:hover,
.approver-config::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 确保滚动区域有足够的最小高度 */
.flow-diagram {
min-height: 300px;
}
.config-section {
margin-bottom: 30px;
}
.config-section h4 {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 15px;
}
.config-section:first-child {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #ebeef5;
}
.no-selection {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}
.no-selection-content {
text-align: center;
color: #909399;
}
.no-selection-content i {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
.no-selection-content p {
margin: 0;
font-size: 14px;
}
.approver-list {
margin-bottom: 20px;
}
.config-section .el-checkbox-group {
margin-bottom: 20px;
}
.approver-groups {
margin-bottom: 20px;
}
.approver-group {
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 16px;
padding: 16px;
background: #fafafa;
}
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.group-title {
font-weight: 600;
color: #303133;
font-size: 14px;
}
.group-content {
display: flex;
gap: 16px;
align-items: flex-end;
}
.group-content .el-form-item {
flex: 1;
margin-bottom: 0;
}
.add-group-btn {
width: 100%;
margin-top: 8px;
}
.cc-config .el-checkbox-group {
margin-bottom: 20px;
}
.cc-groups {
margin-bottom: 20px;
}
.cc-group {
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 16px;
padding: 16px;
background: #fafafa;
}
.department-group {
margin-top: 10px;
}
.cc-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.cc-header h4 {
margin: 0;
font-weight: 600;
color: #303133;
}
.cc-config {
margin-top: 15px;
}
.no-selection {
text-align: center;
color: #909399;
margin-top: 50px;
}
.no-selection p {
margin: 0;
}
.bottom-actions {
display: flex;
justify-content: center;
gap: 20px;
padding: 20px 0;
}
/* 弹框样式优化 */
:deep(.el-dialog__body) {
padding: 20px;
}
:deep(.el-form-item) {
margin-bottom: 20px;
}
:deep(.el-form-item__label) {
font-weight: 600;
color: #303133;
}
:deep(.el-input__inner) {
border-radius: 6px;
}
:deep(.el-select) {
width: 100%;
}
:deep(.el-select .el-input__inner) {
border-radius: 6px;
}
/* 移动端适配 */
@media (max-width: 768px) {
.approval-flow-container {
height: auto;
min-height: 100vh;
padding: 10px;
}
.main-content {
flex-direction: column;
gap: 15px;
}
.left-panel,
.middle-panel,
.right-panel {
width: 100%;
height: auto;
min-height: auto;
padding: 15px;
margin-bottom: 15px;
}
.left-panel {
order: 1;
}
.middle-panel {
order: 2;
min-width: auto;
}
.right-panel {
order: 3;
}
.flow-diagram {
max-height: 400px;
padding: 15px 5px;
}
.node-container {
flex-direction: column;
gap: 10px;
margin: 15px 0;
}
.node-btn {
width: 60px;
height: 60px;
font-size: 18px;
}
.node-info {
min-width: auto;
text-align: center;
}
.node-title {
font-size: 14px;
}
.node-subtitle {
font-size: 12px;
}
.connection-line {
margin: 0px 0;
}
.dotted-line {
height: 75px;
}
.add-node-btn {
width: 40px;
height: 40px;
font-size: 16px;
}
.approver-config {
max-height: 300px;
}
.approver-group,
.cc-group {
padding: 12px;
}
.group-content {
flex-direction: column;
gap: 12px;
}
.group-content .el-form-item {
flex: none;
}
.bottom-actions {
flex-direction: column;
gap: 15px;
padding: 15px 0;
}
.bottom-actions .el-button {
width: 100%;
}
.cc-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.cc-header h4 {
margin-bottom: 0;
}
.panel-title {
font-size: 14px;
margin-bottom: 15px;
}
.form-row {
flex-direction: column;
gap: 15px;
}
.form-row .el-form-item {
flex: none;
}
}
/* 超小屏幕适配 */
@media (max-width: 480px) {
.approval-flow-container {
padding: 5px;
}
.left-panel,
.middle-panel,
.right-panel {
padding: 10px;
}
.node-btn {
width: 50px;
height: 50px;
font-size: 16px;
}
.node-title {
font-size: 13px;
}
.node-subtitle {
font-size: 11px;
}
.flow-diagram {
padding: 10px 5px;
}
.approver-config {
max-height: 250px;
}
.approver-group,
.cc-group {
padding: 10px;
}
}
</style>