|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,38 @@ |
|||
<!-- |
|||
@ 作者: 秦东 |
|||
@ 时间: 2026-01-28 14:29:57 |
|||
@ 备注: 列表视图 |
|||
--> |
|||
<script lang='ts' setup> |
|||
import { attrButton, FormPageList } from '@/api/DesignForm/tableButton'; |
|||
|
|||
|
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
data: FormPageList | any; |
|||
searchData?: attrButton[] | any; |
|||
}>(), |
|||
{ |
|||
showPage: true, |
|||
data: () => { |
|||
return { columns: [], controlBtn: [], operateBtn: [], config: {} }; |
|||
}, |
|||
searchData: () => { |
|||
return []; |
|||
}, |
|||
} |
|||
) |
|||
|
|||
const tableDataList = ref([]); // 表格行数据 |
|||
|
|||
</script> |
|||
<template> |
|||
<div class="app-content"> |
|||
|
|||
</div> |
|||
</template> |
|||
<style lang='scss' scoped> |
|||
.app-content { |
|||
|
|||
} |
|||
</style> |
|||
@ -0,0 +1,281 @@ |
|||
<!-- |
|||
@ 作者: 秦东 |
|||
@ 时间: 2026-01-28 08:25:29 |
|||
@ 备注: |
|||
--> |
|||
<script lang='ts' setup> |
|||
import SvgIcon from "@/components/SvgIcon/index.vue"; |
|||
import { doAiChat,aiChatData} from "@/api/doc/space" |
|||
import { useUserStore } from "@/store/modules/user"; |
|||
import { VueMarkdown } from '@crazydos/vue-markdown' |
|||
import rehypeRaw from 'rehype-raw' |
|||
|
|||
//选中的咨询模式 |
|||
const userStore = useUserStore(); |
|||
const userid="p0"+userStore.userInfoCont.userId; |
|||
const regulaKey=import.meta.env.VITE_REGUL_AI_AGENT; |
|||
const baseURL=import.meta.env.VITE_APP_BASE_API |
|||
const regulaURL =`${baseURL}/aibot/agents/${regulaKey}/chat` |
|||
const conversation=ref("") //当前会话的uuid |
|||
const controller = ref<AbortController | null>(null) |
|||
|
|||
const interact_msg=ref<{ask:boolean,think:string,content:string}[]>([{ask:false,think:"是",content:`你好!我是你的AI助手, |
|||
你可以直接向我提问,或者编辑表单,我会自动为你提供相关信息。`}]) |
|||
|
|||
// 输入框内容 |
|||
const inputText = ref(''); |
|||
const respMsg=ref("") //markdown 内容,问答智能体的动态回复内容 |
|||
|
|||
//消息体 |
|||
interface message{ |
|||
ask:boolean, |
|||
think:string, |
|||
content:string |
|||
} |
|||
//会话记录 |
|||
interface chatRecord{ |
|||
uuid:string, |
|||
agentuuid:string, |
|||
brief:string, |
|||
messages:message[] |
|||
} |
|||
|
|||
|
|||
defineExpose({onSendParamToAI}) |
|||
|
|||
async function onSendParamToAI(arr:Array<{ uuids: string[]; params: { [key: string]: any } }>){ |
|||
//interact_msg.value=[] |
|||
for (let ele of arr){ |
|||
//interact_msg.value.unshift({ask:true,think:"", content:"AI正在分析。。。"}) |
|||
for (let uid of ele.uuids){ |
|||
let para={ |
|||
inputs: { |
|||
"checkType":"travel", |
|||
"checkInfo":JSON.stringify(ele.params) |
|||
}, |
|||
response_mode:"streaming", |
|||
user:userid,//这里已经base64解析了 |
|||
} |
|||
//添加问题记录 |
|||
interact_msg.value.push({ask:true,think:"", content:JSON.stringify(para)}) |
|||
|
|||
await doRequest(`${baseURL}/aibot/assisted/${uid}/workflow`,para) |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function doRequest(furl:string,param:any){ |
|||
let mRespMsg="" |
|||
controller.value = new AbortController(); |
|||
try{ |
|||
const res= await doAiChat( |
|||
furl, |
|||
param, |
|||
controller.value.signal |
|||
) |
|||
|
|||
if (!res.ok) { |
|||
throw new Error(`HTTP ${res.status} ${res.statusText}`); |
|||
} |
|||
|
|||
const reader = res.body!.getReader(); |
|||
const decoder = new TextDecoder('utf-8'); |
|||
|
|||
let chunk = ''; // 行缓存 |
|||
while (true) { |
|||
const {done, value} = await reader.read() |
|||
if (done) break; |
|||
|
|||
// 服务器可能一次返回多行,需要手动按行拆分 |
|||
chunk += decoder.decode(value, {stream: true}); |
|||
const lines = chunk.split(/\n/); |
|||
chunk = lines.pop() || '' |
|||
for (const line of lines) { |
|||
if (!line.trim()) continue; // 跳过空行 |
|||
if (line.startsWith('data: ')) { |
|||
const data = line.slice(6); |
|||
const json = JSON.parse(data); |
|||
if(json.event==="text_chunk"){ |
|||
//conversation.value=json.conversation_id |
|||
mRespMsg+=json.data.text |
|||
}else if(json.event==="message"){ |
|||
respMsg.value+=json.answer |
|||
mRespMsg+=json.answer |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}catch (e: any) { |
|||
if (e.name === 'AbortError') { |
|||
console.log('用户手动中断') |
|||
} |
|||
} |
|||
respMsg.value="" |
|||
const arr=mRespMsg.split("</think>") |
|||
if(arr.length===1){ |
|||
interact_msg.value.push({ask:false,think:"",content:arr[0]}) |
|||
}else{ |
|||
//思考模式 |
|||
let st = arr[1].trim().match(/({.*})/s) |
|||
if (st){ |
|||
let res= JSON.parse(st[1]) |
|||
interact_msg.value.push({ask:false,think:res.success,content:res.reason}) |
|||
}else{ |
|||
interact_msg.value.push({ask:false,think:arr[0],content:arr[1]}) |
|||
} |
|||
} |
|||
scrollToBottom() |
|||
} |
|||
|
|||
function resetContext(){ |
|||
interact_msg.value=[] |
|||
conversation.value="" |
|||
//props.closefunc() |
|||
} |
|||
|
|||
// 发送消息 |
|||
const sendMessage = () => { |
|||
const content = inputText.value.trim(); |
|||
if (!content) return; |
|||
|
|||
// 添加用户消息 |
|||
interact_msg.value.push({ ask: true,think:"", content }); |
|||
|
|||
const params={ |
|||
inputs: {"onlineSearch":"否", |
|||
"useDataset":"是", |
|||
"queryUrl":""}, |
|||
query:content, |
|||
response_mode:"streaming", |
|||
conversation_id:conversation.value, |
|||
user:userid |
|||
} |
|||
|
|||
doRequest(regulaURL,params) |
|||
|
|||
// 清空输入框 |
|||
inputText.value = ''; |
|||
}; |
|||
const scrollbarRef = ref() |
|||
const scrollToBottom = () => { |
|||
const wrap = scrollbarRef.value?.wrapRef // ⚠️ 关键:获取内部滚动容器 |
|||
if (wrap) { |
|||
console.log(wrap.scrollHeight , wrap.clientHeight) |
|||
wrap.scrollTop = wrap.scrollHeight - wrap.clientHeight |
|||
} |
|||
} |
|||
|
|||
</script> |
|||
<template> |
|||
<el-card shadow="always"> |
|||
<template #header> |
|||
<div class="card-header"> |
|||
<div class="svgBox"><SvgIcon icon-class="aiRoboot" size="25" /></div> |
|||
AI智能问答助手 |
|||
</div> |
|||
</template> |
|||
<el-scrollbar ref="scrollbarRef" class="scroBox "> |
|||
|
|||
<div v-for="(msg, index) in interact_msg" :key="index" class="ai-conversation"> |
|||
<div v-if="msg.ask" class="message user-message"> |
|||
<strong>用户:</strong>{{ msg.content }} |
|||
</div> |
|||
<div v-else class="message ai-message"> |
|||
<strong>AI助手:</strong> <VueMarkdown :markdown="msg.content" :rehype-plugins="[rehypeRaw]" ></VueMarkdown> |
|||
</div> |
|||
</div> |
|||
<div class="ai-conversation"> |
|||
<div v-show="respMsg" class="message user-message"> |
|||
<strong>AI助手:</strong><VueMarkdown :markdown="respMsg" :rehype-plugins="[rehypeRaw]" class="t_resp"></VueMarkdown> |
|||
</div> |
|||
</div> |
|||
</el-scrollbar> |
|||
<template #footer> |
|||
<div class="bootemAi"> |
|||
<el-input v-model="inputText" placeholder="请输入您的问题..." /> |
|||
|
|||
<el-button :disabled="!inputText.trim()" class="ai-send-btn" @click="sendMessage"><SvgIcon icon-class="fbsk" size="" /></el-button> |
|||
</div> |
|||
</template> |
|||
</el-card> |
|||
</template> |
|||
<style lang='scss' scoped> |
|||
.card-header{ |
|||
font-size: 1.4rem; |
|||
font-weight: 700; |
|||
color: #0020C2; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10px; |
|||
} |
|||
.svgBox{ |
|||
background: linear-gradient(135deg, #0020C2, #4d6cff); |
|||
color: white; |
|||
width: 40px; |
|||
height: 40px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 1.2rem; |
|||
} |
|||
.scroBox{ |
|||
padding: 10px 15px; |
|||
height: calc(100vh - 200px); |
|||
} |
|||
.ai-conversation { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 15px; |
|||
margin-bottom: 20px; |
|||
} |
|||
.message { |
|||
padding: 12px 16px; |
|||
border-radius: 12px; |
|||
max-width: 90%; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.user-message { |
|||
background: #eef2ff; |
|||
align-self: flex-end; |
|||
border-top-right-radius: 4px; |
|||
border: 1px solid rgba(0, 32, 194, 0.2); |
|||
} |
|||
|
|||
.ai-message { |
|||
background: linear-gradient(to right, #f0f5ff, #ffffff); |
|||
align-self: flex-start; |
|||
border-top-left-radius: 4px; |
|||
border: 1px solid rgba(0, 32, 194, 0.1); |
|||
} |
|||
.ai-send-btn { |
|||
background: linear-gradient(135deg, #0020C2, #4d6cff); |
|||
color: white; |
|||
border: none; |
|||
width: 46px; |
|||
border-radius: 10px; |
|||
cursor: pointer; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.ai-send-btn:hover { |
|||
background: linear-gradient(135deg, #0019a0, #3a5aff); |
|||
transform: translateY(-2px); |
|||
} |
|||
.bootemAi{ |
|||
display: grid; |
|||
grid-template-columns: 1fr 50px; /* 左右最小150px,最大250px,中间自适应 */ |
|||
grid-template-rows: auto; |
|||
gap: 10px; |
|||
max-width: 100%; |
|||
margin: 0 auto; |
|||
:deep .el-input__wrapper{ |
|||
border-radius: 10px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,427 @@ |
|||
<!-- |
|||
@ 作者: 秦东 |
|||
@ 时间: 2026-01-27 16:11:23 |
|||
@ 备注: |
|||
--> |
|||
<script lang='ts' setup> |
|||
import SvgIcon from "@/components/SvgIcon/index.vue"; |
|||
import OrgUserPage from "@/views/public/orguser/orguser.vue" |
|||
import OrgAllUserPage from "@/views/public/orguser/orgalluser.vue" |
|||
import squareUrlOne from "@/assets/images/1.png" |
|||
import squareUrlTwo from "@/assets/images/2.png" |
|||
|
|||
const activeStep = ref(1) |
|||
const state = reactive({ |
|||
circleUrl:squareUrlTwo, |
|||
squareUrl: squareUrlOne, |
|||
sizeList: ['small', '', 'large'] as const, |
|||
}) |
|||
const { circleUrl, squareUrl, sizeList } = toRefs(state) |
|||
const openOrClose = ref(false) |
|||
const openclosebox = ref(false) |
|||
const ifSendFlow = ref(false) |
|||
const props = defineProps({ |
|||
flowMap: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
nodeKey:{ |
|||
type:String, |
|||
default:"" |
|||
}, |
|||
currentProgress:{ |
|||
type:Number, |
|||
default:0 |
|||
} |
|||
, |
|||
nextStep:{ |
|||
type:Number, |
|||
default:0 |
|||
} |
|||
}) |
|||
const presetPersonnel = ref<any>([]); //预设选择人 |
|||
const selectedPeople = ref<any>([]); //已选择人 |
|||
const flowMaps = ref<any[]>(); |
|||
const emits = defineEmits(["update:flowMap"]); |
|||
const flowList = computed<any>({ |
|||
get: () => props.flowMap, |
|||
set: (val) => { |
|||
emits("update:flowMap", val); |
|||
}, |
|||
}); |
|||
watch(()=>props.flowMap,(val:any)=>{ |
|||
|
|||
emits("update:flowMap", val); |
|||
}) |
|||
onMounted(()=>{ |
|||
|
|||
}) |
|||
//判断是否有增加人员按钮 |
|||
const judgeAddUser = (val:any):boolean =>{ |
|||
console.log("val----->",val) |
|||
if(val.customNode == "beginnode"){ |
|||
val.runscope = val.runscope!=0?val.runscope:1 |
|||
return true |
|||
} |
|||
switch(val.runtype){ |
|||
case 1: |
|||
val.runscope = val.runscope!=0?val.runscope:1 |
|||
return true |
|||
break; |
|||
case 3: |
|||
if(val.customNode == props.nodeKey){ |
|||
val.runscope = val.runscope!=0?val.runscope:1 |
|||
return true |
|||
} |
|||
break; |
|||
case 4: |
|||
if(val.runscope==1){ |
|||
val.runscope = val.runscope!=0?val.runscope:1 |
|||
return true |
|||
} |
|||
break; |
|||
default: |
|||
// if(!val.operator){ |
|||
// val.runscope = val.runscope!=0?val.runscope:1 |
|||
// return true |
|||
// }else if(val.operator.length < 1){ |
|||
// val.runscope = val.runscope!=0?val.runscope:1 |
|||
// return true |
|||
// } |
|||
if(val.runscope != 0){ |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
|
|||
return false |
|||
} |
|||
let zhiXingStep = 1; |
|||
//添加操作人 |
|||
const addPeople = (val:any) =>{ |
|||
zhiXingStep = val.step |
|||
presetPersonnel.value = val.pendpers |
|||
selectedPeople.value = val.operator |
|||
if(val.runscope == 1){ |
|||
openclosebox.value = true |
|||
}else{ |
|||
openOrClose.value = true |
|||
} |
|||
|
|||
// console.log("PresetPersonnel.value--------1-------->",val) |
|||
// console.log("PresetPersonnel.value--------2-------->",val.pendpers) |
|||
// console.log("PresetPersonnel.value--------3-------->",val.operator) |
|||
// console.log("PresetPersonnel.value--------4-------->",selectedPeople.value) |
|||
} |
|||
//更新节点操作人 |
|||
const updateNode = (val:any) =>{ |
|||
// console.log("P更新节点操作人",zhiXingStep,val) |
|||
flowList.value.forEach((item:any) =>{ |
|||
if(item.step == zhiXingStep){ |
|||
// console.log("P更新节点操作人---1-->",item.step , zhiXingStep) |
|||
item.operator = val |
|||
} |
|||
}) |
|||
// console.log("P更新节点操作人--2--->",flowList) |
|||
} |
|||
|
|||
/** |
|||
@ 作者: 秦东 |
|||
@ 时间: 2026-01-29 16:37:56 |
|||
@ 功能: 判断审批节点样式 |
|||
*/ |
|||
const judgeNodeClass = (val:number,stepVal:number):string => { |
|||
console.log("判断审批节点样式",val,stepVal) |
|||
switch(val){ |
|||
case 1: |
|||
if(props.currentProgress == stepVal){ |
|||
return "in-progress" |
|||
}else{ |
|||
return 'completed' |
|||
} |
|||
case 2: |
|||
return 'pending' |
|||
case 3: |
|||
// return 'in-progress' |
|||
if(props.currentProgress == stepVal){ |
|||
return "in-progress" |
|||
}else{ |
|||
return 'in-executor' |
|||
} |
|||
default: |
|||
if(props.currentProgress == stepVal){ |
|||
return "in-progress" |
|||
}else{ |
|||
return 'completed' |
|||
} |
|||
|
|||
} |
|||
return 'completed' |
|||
} |
|||
|
|||
|
|||
</script> |
|||
<template > |
|||
<el-card shadow="always"> |
|||
<template #header> |
|||
<div class="card-header"> |
|||
<div class="svgBox"><SvgIcon icon-class="liuChengBiaoDan" size="25" /></div> |
|||
审批流程 |
|||
</div> |
|||
</template> |
|||
<el-scrollbar :class="ifSendFlow?'flowBody':'flowBodyNofoot'"> |
|||
|
|||
<div class="approval-steps"> |
|||
<div v-for="item in flowList" :key="item.step" :class="['step', judgeNodeClass(item.type,item.step)]"> |
|||
<div class="step-icon"> |
|||
<SvgIcon v-if="item.type==0" icon-class="faqiren" size="25" /> |
|||
<SvgIcon v-if="item.type==1" icon-class="spr" size="25" /> |
|||
<SvgIcon v-if="item.type==2" icon-class="csr" size="25" /> |
|||
<SvgIcon v-if="item.type==3" icon-class="zxr" size="25" /> |
|||
<!--SvgIcon v-else icon-class="shenpi" size="25" /--> |
|||
</div> |
|||
<div class="step-content"> |
|||
<h3 class="step-title">{{item.nodeName}}</h3> |
|||
<div v-for="items in item.operator" :key="items.id" class="flowLogBox"> |
|||
|
|||
<div > |
|||
<el-avatar v-if="items.icon!=''" shape="square" fit="cover" :src="items.icon" style="width: 40px;" class="avatarBox" /> |
|||
<el-avatar v-else-if="items.iconbase64!=''" shape="square" fit="cover" :src="items.iconbase64" style="width: 40px;" class="avatarBox" /> |
|||
<el-avatar v-else shape="square" fit="cover" :src="squareUrl" style="width: 40px;" class="avatarBox" /> |
|||
</div> |
|||
<div> |
|||
<div> |
|||
<el-text size="small">{{ items.departmentname }}</el-text> |
|||
<el-text size="small"><span v-if="items.departmentname"> - </span>{{ items.postname }}</el-text> |
|||
<el-text size="small"><span v-if="items.departmentname||items.postname"> - </span>{{ items.name }}</el-text> |
|||
</div> |
|||
<div v-for="(logItem,logIndex) in items.log" :key="logIndex" > |
|||
<div v-if="logItem.state==2" class="step-info"> |
|||
<el-text v-if="logItem.cause" type="success" size="small">{{logItem.cause}}</el-text> |
|||
<el-text v-else type="success" size="small" >已同意</el-text> |
|||
</div> |
|||
<div v-if="logItem.state==3" class="step-info"> |
|||
<el-text v-if="logItem.cause" type="danger" size="small">{{logItem.cause}}</el-text> |
|||
<el-text v-else type="danger" size="small" >已驳回</el-text> |
|||
</div> |
|||
<div v-else class="step-info"> |
|||
<el-text v-if="logItem.cause" size="small">{{logItem.cause}}</el-text> |
|||
<el-text v-else size="small" >未操作</el-text> |
|||
</div> |
|||
<div class="step-time">{{ logItem.time }}</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<div v-if="judgeAddUser(item)" class="addUser" @click="addPeople(item)"> |
|||
<svg-icon icon-class="addxuxian" size="50" /> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
</el-scrollbar> |
|||
<!-- <template #footer> |
|||
<div class="bootemWorkFlow"> |
|||
<el-input type="textarea" :rows="2" style="width: 100%" placeholder="请输入审批意见"></el-input> |
|||
<div class="bootemWorkFlowBut"> |
|||
<el-button class="btn approve-btn"><SvgIcon icon-class="kxdg" size="" style="margin-right: 5px" />通过审批</el-button> |
|||
<el-button class="btn reject-btn"><SvgIcon icon-class="cwkx" size="" style="margin-right: 5px" />驳回申请</el-button> |
|||
</div> |
|||
</div> |
|||
</template> --> |
|||
</el-card> |
|||
|
|||
<OrgUserPage v-if="openOrClose" v-model:openclose="openOrClose" :preset-personnel="presetPersonnel" :selected-people="selectedPeople" @update-node="updateNode" /> |
|||
<OrgAllUserPage v-if="openclosebox" v-model:openclosebox="openclosebox" :selected-people="selectedPeople" @update-node="updateNode" /> |
|||
|
|||
</template> |
|||
<style lang='scss' scoped> |
|||
.card-header{ |
|||
font-size: 1.4rem; |
|||
font-weight: 700; |
|||
color: #0020C2; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10px; |
|||
} |
|||
.flowBody{ |
|||
padding: 10px 15px; |
|||
height: calc(100vh - 260px); |
|||
} |
|||
.flowBodyNofoot{ |
|||
padding: 10px 15px; |
|||
height: calc(100vh - 140px); |
|||
} |
|||
.svgBox{ |
|||
background: linear-gradient(135deg, #0020C2, #4d6cff); |
|||
color: white; |
|||
width: 40px; |
|||
height: 40px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 1.2rem; |
|||
} |
|||
.bootemWorkFlow{ |
|||
width: 100%; |
|||
text-align: center; |
|||
.bootemWorkFlowBut{ |
|||
width: 100%; |
|||
padding: 10px 0; |
|||
text-align: center; |
|||
} |
|||
} |
|||
|
|||
.btn { |
|||
padding: 14px 16px; |
|||
border-radius: 10px; |
|||
font-weight: 600; |
|||
font-size: 1rem; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
border: none; |
|||
} |
|||
.btn:hover { |
|||
transform: translateY(-2px); |
|||
box-shadow: 0 5px 15px rgba(0, 32, 194, 0.15); |
|||
} |
|||
.approve-btn { |
|||
background: linear-gradient(135deg, #00cc66, #33dd88); |
|||
color: white; |
|||
} |
|||
.reject-btn { |
|||
background: linear-gradient(135deg, #ff3366, #ff5588); |
|||
color: white; |
|||
} |
|||
.approval-steps { |
|||
flex: 1; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
.step { |
|||
display: flex; |
|||
margin-bottom: 25px; |
|||
position: relative; |
|||
margin-left: 7px; |
|||
margin-top: 7px; |
|||
} |
|||
|
|||
.step:not(:last-child)::after { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 20px; |
|||
top: 40px; |
|||
width: 2px; |
|||
height: calc(100% + 5px); |
|||
background: linear-gradient(to bottom, rgba(0, 32, 194, 0.2), rgba(0, 32, 194, 0.05)); |
|||
} |
|||
|
|||
.step-icon { |
|||
width: 40px; |
|||
height: 40px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-right: 5px; |
|||
z-index: 1; |
|||
flex-shrink: 0; |
|||
|
|||
} |
|||
|
|||
.step.completed .step-icon { |
|||
background: linear-gradient(135deg, #0020C2, #4d6cff); |
|||
color: white; |
|||
} |
|||
|
|||
.step.in-progress .step-icon { |
|||
background: linear-gradient(135deg, #ffcc00, #ffdd55); |
|||
color: #333; |
|||
animation: pulse 2s infinite; |
|||
} |
|||
|
|||
.step.in-executor .step-icon { |
|||
background: linear-gradient(135deg, #67C23A, #95f764); |
|||
color: #fff; |
|||
} |
|||
|
|||
.step.pending .step-icon { |
|||
background: #f0f4ff; |
|||
color: #a0a6c9; |
|||
} |
|||
|
|||
.step-content { |
|||
flex: 1; |
|||
} |
|||
|
|||
.step-title { |
|||
font-weight: 600; |
|||
margin-bottom: 5px; |
|||
color: #0020C2; |
|||
} |
|||
|
|||
.step.completed .step-title { |
|||
color: #4d6cff; |
|||
} |
|||
|
|||
.step.in-progress .step-title { |
|||
color: #ff9900; |
|||
} |
|||
.step.in-executor .step-title { |
|||
color: #67C23A; |
|||
} |
|||
|
|||
.step.pending .step-title { |
|||
color: #a0a6c9; |
|||
} |
|||
|
|||
.step-info { |
|||
font-size: 0.9rem; |
|||
color: #666; |
|||
margin-top: 0px; |
|||
} |
|||
|
|||
.step-time { |
|||
font-size: 0.8rem; |
|||
color: #888; |
|||
margin-bottom: 4px; |
|||
} |
|||
/* 响应式设计 */ |
|||
@media (max-width: 1200px) { |
|||
.container { |
|||
grid-template-columns: 1fr; |
|||
grid-template-rows: auto auto auto; |
|||
height: auto; |
|||
gap: 15px; |
|||
} |
|||
|
|||
.card { |
|||
min-height: 400px; |
|||
} |
|||
} |
|||
|
|||
@keyframes pulse { |
|||
0% { box-shadow: 0 0 0 0 rgba(255, 204, 0, 0.7); } |
|||
70% { box-shadow: 0 0 0 10px rgba(255, 204, 0, 0); } |
|||
100% { box-shadow: 0 0 0 0 rgba(255, 204, 0, 0); } |
|||
} |
|||
.flowLogBox{ |
|||
display: grid; |
|||
grid-template-columns: 45px 1fr; |
|||
} |
|||
.addUser{ |
|||
display:block; |
|||
cursor:pointer; |
|||
} |
|||
.avatarBox{ |
|||
border: 1px solid rgba(255, 255, 255, 0.5); |
|||
} |
|||
</style> |
|||