Files
art-design/src/views/admin/approval/approval_edit.vue
T

1374 lines
34 KiB
Vue
Raw Normal View History

2025-09-18 10:57:17 +08:00
<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>