|
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,634 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2026-01-30 16:42:49 |
||||
|
@ 备注: 112312 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { afreshRunWorkflow, draftsInitiateApproval, gainEditDataLog, gainTaskFormInfo } from '@/api/taskapi/management'; |
||||
|
import { string2json, stringToObj } from '@/utils/DesignForm/form'; |
||||
|
import { constAiEffect } from "@/api/DesignForm/utils"; |
||||
|
import { customerFormVersionCont, nodePoweInfo } from "@/api/taskapi/types"; |
||||
|
|
||||
|
import RunFlowStep from "@/views/taskplatform/taskmanagement/runNewFlowStep.vue"; |
||||
|
import AiPage from "@/views/sysworkflow/lowcodepage/pageFlow/aiPage.vue"; |
||||
|
import { getFieldRecord, judgeSubmitCancel } from '@/api/DesignForm/requestapi'; |
||||
|
import { afreshSubmitButton, draftSubmitButton, editFormCont } from '@/utils/workflow/const'; |
||||
|
import { submitButtonEs } from '@/api/DesignForm/tableButton'; |
||||
|
|
||||
|
|
||||
|
const props = defineProps({ |
||||
|
isShow: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
drawerWith: { |
||||
|
type: Number, |
||||
|
default: 0, |
||||
|
}, |
||||
|
pageInfo: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return {}; |
||||
|
}, |
||||
|
}, |
||||
|
tablePageClass: { |
||||
|
type: Number, |
||||
|
default: 1, |
||||
|
}, |
||||
|
operState: { |
||||
|
type: Number, |
||||
|
default: 1, |
||||
|
}, |
||||
|
dprt: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return {}; |
||||
|
}, |
||||
|
}, |
||||
|
versiontitle: { |
||||
|
type: String, |
||||
|
default: "", |
||||
|
} |
||||
|
}) |
||||
|
const drawTitle = computed(() => props.versiontitle) |
||||
|
const drawBodyWidth = ref(props.drawerWith) |
||||
|
const emits = defineEmits(["update:isShow", "getPageData", "optionsValue4Get4"]); |
||||
|
const drawLoading = ref(false) |
||||
|
const aiConfigArea =ref(false) //是否有AI |
||||
|
const isFlowTable = ref(false); //判断是不是流程表单 |
||||
|
let gainTaskFormInfoPromise: any[] = []; |
||||
|
const runstep = ref(); |
||||
|
const flowAry = ref<any[]>(); //流程列表 |
||||
|
const currentProgress = ref<number>(1); //当前步进值 |
||||
|
//表单相关内容 |
||||
|
const formState = reactive<any>({ |
||||
|
formData: { |
||||
|
list: [], |
||||
|
form: {}, |
||||
|
config: {}, |
||||
|
powerstr: {}, |
||||
|
aiConfig:[] |
||||
|
}, |
||||
|
dict: {}, |
||||
|
formId: props.pageInfo.id, |
||||
|
id: 1, |
||||
|
loading: true, |
||||
|
type: props.tablePageClass, |
||||
|
}); |
||||
|
let gainTaskFormInfoData: customerFormVersionCont; |
||||
|
const currterNodePower = ref<nodePoweInfo[]>([]) |
||||
|
const openOrClose = computed({ |
||||
|
get: () => { |
||||
|
return props.isShow |
||||
|
}, |
||||
|
set: (val) => { |
||||
|
emits("update:isShow", val); |
||||
|
drawBodyWidth.value = 0; |
||||
|
}, |
||||
|
}); |
||||
|
const nodeKey = ref<string>(""); |
||||
|
//-----------------------AI setting-------------------------- |
||||
|
//当前AI智能体,根据form表单的配置来初始化 |
||||
|
//params 主表字段参数 subparams子表字段参数,子表触发时需要清空 ; rowdex:表示子表的第几条数据,没有的话说明不是子表数据 |
||||
|
const currentAgent = ref< |
||||
|
{ |
||||
|
model: boolean; |
||||
|
name: string; |
||||
|
rowdex:number, |
||||
|
uuid: string[]; |
||||
|
fields: string[]; |
||||
|
trigger: number; |
||||
|
params: { [key: string]: any }; |
||||
|
subparams:{[key: string]: any}[]; |
||||
|
}[] |
||||
|
>([]); |
||||
|
const aiassistRef = ref(); |
||||
|
//触发AI查询事件 |
||||
|
provide(constAiEffect, ({ key, value, field,rowdex}: any) => { |
||||
|
//新建ai_envents 是为了解决前端并发问题 |
||||
|
const ai_events: Array<{ uuids: string[]; params: { [key: string]: any } }> = []; |
||||
|
currentAgent.value.forEach(ag=>{ |
||||
|
if(ag.fields.includes(key)){ //trigger: 1:任一 2:半数 3:全部 |
||||
|
if (rowdex!=null){//子表操作 |
||||
|
ag.rowdex=rowdex //更新智能体的rowdex |
||||
|
if(rowdex>=ag.subparams.length){ |
||||
|
ag.subparams.push({[field]:value}) |
||||
|
}else{ |
||||
|
ag.subparams[rowdex][field]=value |
||||
|
} |
||||
|
}else{ //主表操作, 直接加入params种 |
||||
|
ag.params[field]=value; //这个地方把key换成fieldName |
||||
|
} |
||||
|
let mergedObj; |
||||
|
if(ag.subparams.length>0){ |
||||
|
mergedObj = Object.assign({}, ag.params, ag.subparams[ag.rowdex]); //合并子表和主表 |
||||
|
}else{ |
||||
|
mergedObj = ag.params |
||||
|
} |
||||
|
switch(ag.trigger){ |
||||
|
case 2: |
||||
|
if(Object.keys(mergedObj).length>=ag.fields.length/2){ |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj }) |
||||
|
} |
||||
|
break; |
||||
|
case 3: |
||||
|
if(Object.keys(mergedObj).length==ag.fields.length){ |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj}) |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
if(ai_events.length>0){ |
||||
|
aiassistRef.value.onSendParamToAI(ai_events) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
/** |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2024-04-03 14:33:15 |
||||
|
@ 功能: 获取自定义表单内容 |
||||
|
*/ |
||||
|
const getCustomrrFormInfo = () => { |
||||
|
gainTaskFormInfoPromise.push( |
||||
|
gainTaskFormInfo({ id: props.pageInfo.masters_key,runFlowId: props.pageInfo.runFlowId }) |
||||
|
.then(({ data }) => { |
||||
|
let extraW=0; |
||||
|
console.log("获取自定义表单内容--->",data) |
||||
|
formState.formData = stringToObj(data.structure.mastesform); |
||||
|
console.log("获取自定义表单内容--state.formData->1", formState.formData); |
||||
|
if(isFlowTable.value){ |
||||
|
extraW = 320 |
||||
|
} |
||||
|
if(formState.formData&&formState.formData.aiConfig&&formState.formData.aiConfig.length>0){ |
||||
|
aiConfigArea.value=true |
||||
|
formState.formData.aiConfig?.forEach((item: { library: any; title: any; trigger: any; }) => { |
||||
|
currentAgent.value.push({ |
||||
|
name: "test", |
||||
|
model: false, |
||||
|
rowdex:0, |
||||
|
uuid: item.library, |
||||
|
fields: item.title, |
||||
|
trigger: item.trigger, |
||||
|
params: {}, |
||||
|
subparams:[] |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
if(aiConfigArea.value){ |
||||
|
extraW = extraW + 320 |
||||
|
} |
||||
|
console.log("获取自定义表单内容--drawBodyWidth.value->2", drawBodyWidth.value); |
||||
|
if (aiConfigArea.value && isFlowTable.value){ |
||||
|
drawBodyWidth.value = drawBodyWidth.value + extraW - 160 |
||||
|
}else if ((!aiConfigArea.value && isFlowTable.value) || (aiConfigArea.value && !isFlowTable.value)){ |
||||
|
drawBodyWidth.value = drawBodyWidth.value + extraW |
||||
|
}else{ |
||||
|
drawBodyWidth.value = drawBodyWidth.value |
||||
|
} |
||||
|
props.dprt.forEach((element: any[]) => { |
||||
|
let tableName = element[0] |
||||
|
let datePickerName = element[1] |
||||
|
data.tableData[tableName].forEach((item: any) => { |
||||
|
let arr = [] |
||||
|
arr.push(item[datePickerName]) |
||||
|
arr.push(item[datePickerName+"_end"]) |
||||
|
item[datePickerName] = arr |
||||
|
}) |
||||
|
}) |
||||
|
gainTaskFormInfoData = data; |
||||
|
nodeKey.value = data.nodeKey?data.nodeKey:""; |
||||
|
if(data.structure.powerAry && Array.isArray(data.structure.powerAry) && data.structure.powerAry.length > 0){ |
||||
|
currterNodePower.value = data.structure.powerAry |
||||
|
} |
||||
|
|
||||
|
console.log("获取自定义表单内容--props.tablePageClass->2", props.tablePageClass); |
||||
|
if(props.tablePageClass != 4){ |
||||
|
judgeSubmitCancel({"name":data.structure.mastesformjson}) |
||||
|
.then((data:any) =>{ |
||||
|
console.log("获取自定义表单内容--props.tablePageClass->data", data); |
||||
|
if(data.code == 0){ |
||||
|
if (data.data.buttonIsTrue == 3 || data.data.buttonIsTrue == 4){ |
||||
|
// formState.formData.list.push(submitButtonEs) |
||||
|
if(props.operState == 2){ |
||||
|
formState.formData.list.push(afreshSubmitButton) |
||||
|
}else if(props.operState == 3){ |
||||
|
formState.formData.list.push(editFormCont) |
||||
|
}else if(props.operState == 4){ |
||||
|
formState.formData.list.push(draftSubmitButton) |
||||
|
}else if(props.operState == 5){ |
||||
|
formState.formData.list.push(submitButtonEs) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
nextTick(()=>{ |
||||
|
console.log("获取自定义表单内容-1111-->",data.tableData) |
||||
|
formEl.value.setValue(data.tableData) |
||||
|
}) |
||||
|
|
||||
|
}) |
||||
|
.finally(() => { |
||||
|
|
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
const formLoading = ref(false); |
||||
|
// 监听props.drawerWith的变化,更新drawBodyWidth |
||||
|
watch(() => props.drawerWith, (newVal) => { |
||||
|
drawBodyWidth.value = newVal; |
||||
|
}); |
||||
|
let objMastesform: any; |
||||
|
let getFieldRecordPromises: any[] = []; |
||||
|
let transferDetails: any = []; |
||||
|
const formEl = ref<any>(); |
||||
|
// 监听props.isShow的变化,当isShow为true时,更新drawBodyWidth |
||||
|
watch(() => props.isShow, (newVal) => { |
||||
|
if (newVal) { |
||||
|
if(props.pageInfo.flowIsOpens == 1){ |
||||
|
isFlowTable.value = true |
||||
|
}else{ |
||||
|
isFlowTable.value = false |
||||
|
} |
||||
|
drawBodyWidth.value = props.drawerWith; |
||||
|
getCustomrrFormInfo(); |
||||
|
|
||||
|
} |
||||
|
}); |
||||
|
interface TreeNode { |
||||
|
id: string; |
||||
|
label: string; |
||||
|
disabled: boolean; |
||||
|
children: TreeNode[]; |
||||
|
} |
||||
|
const pageLog = ref<any[]>([]); |
||||
|
function mapIdsToLabels(treeNodes: TreeNode[], ids1: string): string[] { |
||||
|
/* console.log(treeNodes) |
||||
|
console.log(ids1) */ |
||||
|
const ids: string[] = JSON.parse(ids1); |
||||
|
// 创建id到label的映射字典 |
||||
|
const idToLabelMap: Record<string, string> = {}; |
||||
|
|
||||
|
// 递归遍历树节点并填充映射字典 |
||||
|
function traverse(node: TreeNode) { |
||||
|
idToLabelMap[node.id] = node.label; // 存储当前节点的映射 |
||||
|
node.children.forEach((child) => traverse(child)); // 递归处理子节点 |
||||
|
} |
||||
|
|
||||
|
// 遍历根节点列表 |
||||
|
treeNodes.forEach((root) => traverse(root)); |
||||
|
//console.log(ids) |
||||
|
// 将id数组转换为label数组 |
||||
|
return ids.map((id) => idToLabelMap[id]); |
||||
|
} |
||||
|
//流程执行 |
||||
|
const drawerBeforeClose = () => { |
||||
|
// console.log("监听打开关闭",false) |
||||
|
// emits("getmytodolist") |
||||
|
// emits("update:isshow", false); |
||||
|
}; |
||||
|
/** |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2024-04-09 16:49:09 |
||||
|
@ 功能: 表单前置数据 |
||||
|
*/ |
||||
|
const beforeSubmit = (params: any) => { |
||||
|
// params.formId = props.versionid; |
||||
|
// params.id = ""; |
||||
|
// emits("update:isopen", false); |
||||
|
return params; |
||||
|
}; |
||||
|
/** |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2024-04-07 10:55:34 |
||||
|
@ 功能: 表单动作回调 |
||||
|
*/ |
||||
|
const afterSubmit = (type: string) => { |
||||
|
// console.log("表单提交成功") |
||||
|
if (type === "success") { |
||||
|
// router.go(-1) |
||||
|
// console.log("表单提交成功") |
||||
|
emits("getPageData"); |
||||
|
closeAppSubmit(); |
||||
|
} |
||||
|
}; |
||||
|
//组件解析 |
||||
|
const changeKeyVal = (key: any, val: any, type: any, attribute: any) => { |
||||
|
|
||||
|
} |
||||
|
//重新提交审核 |
||||
|
const anewSubmit = (type: string, val?: any) => { |
||||
|
// console.log("重新提交数据-11--<",type,val) |
||||
|
if (type == "success") { |
||||
|
afreshRunWorkflow({ id: val.data.runFlowId }) |
||||
|
.then((data: any) => { |
||||
|
// console.log("重新提交数据---<",data,runstep.value) |
||||
|
runstep.value.gainRunFlowTask(); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
emits("getPageData"); |
||||
|
closeAppSubmit(); |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
//保存草稿箱(带流程) |
||||
|
const saveEditFormInfo = (type: string, val?: any) => { |
||||
|
// console.log("草稿提交审批--1-<",type,val) |
||||
|
if (type == "success") { |
||||
|
runstep.value.gainRunFlowTask(); |
||||
|
emits("getPageData"); |
||||
|
closeAppSubmit(); |
||||
|
} |
||||
|
}; |
||||
|
/** |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2024-04-09 16:50:01 |
||||
|
@ 功能: 关闭操作 |
||||
|
*/ |
||||
|
const closeAppSubmit = () => { |
||||
|
emits("update:isShow", false) |
||||
|
}; |
||||
|
//提交审批 |
||||
|
const sendDraftSubmit = (type: string, val?: any) => { |
||||
|
console.log("提交审批",type,val) |
||||
|
console.log(new Error("请在表单设计处配置接口事件url或选择数据源或设置props11111")); |
||||
|
// emits("getmytodolist") |
||||
|
if (type === "success") { |
||||
|
console.log("草稿提交审批--2-<",props.pageInfo ) |
||||
|
if (props.pageInfo.flowIsOpens == 1) { |
||||
|
let sendInfo = { |
||||
|
id: val.data.runFlowId, |
||||
|
flowList: flowAry.value, |
||||
|
state: 3, |
||||
|
}; |
||||
|
console.log("草稿提交审批--1-<",sendInfo) |
||||
|
draftsInitiateApproval(sendInfo).then((data: any) => { |
||||
|
console.log("草稿提交审批---<",sendInfo,data) |
||||
|
|
||||
|
if (data.code == 0) { |
||||
|
ElMessage.success(data.msg || "提交成功!"); |
||||
|
if (runstep.value) { |
||||
|
runstep.value.gainRunFlowTask(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
emits("getPageData"); |
||||
|
closeAppSubmit(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
function optionsValue3Get3(data: any, fieldName: any) { |
||||
|
//console.log("formPageCont","optionsValue3Get3") |
||||
|
emits("optionsValue4Get4", data, fieldName); |
||||
|
} |
||||
|
//修改完毕 |
||||
|
const submitEdit = (type: string, val?: any) => { |
||||
|
// console("修改完毕----->",type,val) |
||||
|
if (type == "success") { |
||||
|
runstep.value.gainRunFlowTask(); |
||||
|
emits("getPageData"); |
||||
|
closeAppSubmit(); |
||||
|
} |
||||
|
}; |
||||
|
// onMounted(() => { |
||||
|
// getCustomrrFormInfo(); |
||||
|
// }) |
||||
|
</script> |
||||
|
<template> |
||||
|
<div v-if="openOrClose" class="drawerClass"> |
||||
|
<el-drawer |
||||
|
v-model="openOrClose" |
||||
|
:title="drawTitle" |
||||
|
:close-on-click-modal="false" |
||||
|
:close-on-press-escape="false" |
||||
|
:destroy-on-close="true" |
||||
|
:size="drawBodyWidth" |
||||
|
:close="closeAppSubmit" |
||||
|
> |
||||
|
<template #header> |
||||
|
<div class="drawHeader"> |
||||
|
<el-text size="large">{{ drawTitle }}</el-text> |
||||
|
</div> |
||||
|
</template> |
||||
|
<div v-loading="drawLoading" :class="aiConfigArea&&isFlowTable?'drawBody treePage':(aiConfigArea&&!isFlowTable)?'drawBody twoPageAi':(!aiConfigArea&&isFlowTable)?'drawBody twoPageFlow':'drawBody'"> |
||||
|
<!-- {{ formState.formData }} --> |
||||
|
<AiPage |
||||
|
v-if="formState.type != 5 && aiConfigArea" |
||||
|
ref="aiassistRef" |
||||
|
:agent="currentAgent" |
||||
|
/> |
||||
|
<AppForm |
||||
|
ref="formEl" |
||||
|
:draw-title="drawTitle" |
||||
|
:form-data="formState.formData" |
||||
|
:masters_key="props.pageInfo.masters_key" |
||||
|
:type="props.tablePageClass" |
||||
|
:dict="formState.dict" |
||||
|
request-url="getFormContent" |
||||
|
add-url="saveFormContent" |
||||
|
edit-url="editFormContent" |
||||
|
:before-submit="beforeSubmit" |
||||
|
:after-submit="afterSubmit" |
||||
|
:close-app-submit="closeAppSubmit" |
||||
|
:change-key-val="changeKeyVal" |
||||
|
:anew-submit="anewSubmit" |
||||
|
:save-edit-form-info="saveEditFormInfo" |
||||
|
:send-draft-submit="sendDraftSubmit" |
||||
|
:submit-edit="submitEdit" |
||||
|
@options-value3-get3="optionsValue3Get3" |
||||
|
/> |
||||
|
<RunFlowStep |
||||
|
v-if="isFlowTable" |
||||
|
ref="runstep" |
||||
|
v-model:flowary="flowAry" |
||||
|
:flow-key="props.pageInfo.runFlowId" |
||||
|
:current-progress="currentProgress" |
||||
|
@updatelist="drawerBeforeClose" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
</el-drawer> |
||||
|
</div> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
.drawerClass{ |
||||
|
:deep .el-drawer__header{ |
||||
|
border-bottom: 1px solid #ECECEC; |
||||
|
} |
||||
|
} |
||||
|
.drawHeader { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.drawBody{ |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
padding: 10px 10px; |
||||
|
:deep .el-card{ |
||||
|
border-radius: 15px; |
||||
|
} |
||||
|
:deep .el-card__header{ |
||||
|
padding: 15px 15px; |
||||
|
} |
||||
|
:deep .el-card__body{ |
||||
|
padding: 0; |
||||
|
} |
||||
|
:deep .el-card__footer{ |
||||
|
padding: 10px 10px; |
||||
|
} |
||||
|
} |
||||
|
.twoPageAi{ |
||||
|
display: grid; |
||||
|
grid-template-columns: minmax(150px, 250px) 1fr; /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.twoPageFlow{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr minmax(150px, 250px); /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.treePage{ |
||||
|
display: grid; |
||||
|
grid-template-columns: minmax(150px, 250px) 1fr minmax(150px, 250px); /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.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; |
||||
|
} |
||||
|
.card-header{ |
||||
|
font-size: 1.4rem; |
||||
|
font-weight: 700; |
||||
|
color: #0020C2; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
.scroBox{ |
||||
|
padding: 10px 15px; |
||||
|
height: calc(100vh - 200px); |
||||
|
} |
||||
|
.formBody{ |
||||
|
width: 100%; |
||||
|
overflow-x: auto; |
||||
|
} |
||||
|
.flowBody{ |
||||
|
padding: 10px 15px; |
||||
|
height: calc(100vh - 260px); |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.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); |
||||
|
} |
||||
|
.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); |
||||
|
} |
||||
|
</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> |
||||
@ -0,0 +1,523 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2026-02-05 13:51:29 |
||||
|
@ 备注: 代办事宜 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { |
||||
|
submitButton, |
||||
|
afreshSubmitButton, |
||||
|
editFormCont, |
||||
|
draftSubmitButton, |
||||
|
editLookFormCont, |
||||
|
} from "@/utils/workflow/const"; |
||||
|
import { |
||||
|
json2string, |
||||
|
objToStringify, |
||||
|
string2json, |
||||
|
stringToObj, |
||||
|
} from "@/utils/DesignForm/form"; |
||||
|
|
||||
|
import { |
||||
|
haveCustomerFormVersion, |
||||
|
generateFlow, |
||||
|
gainTaskFormInfo, |
||||
|
gainEditDataLog, |
||||
|
} from "@/api/taskapi/management"; |
||||
|
|
||||
|
import RunFlowStepApp from "@/views/taskplatform/taskmanagement/runFlowStepApp.vue"; |
||||
|
|
||||
|
import { |
||||
|
judgeSubmitCancel, |
||||
|
startRunFlow, |
||||
|
afreshRunFlow, |
||||
|
onlyPublishFlow, |
||||
|
gainEditFormFlowInfo, |
||||
|
} from "@/api/DesignForm/requestapi"; |
||||
|
|
||||
|
import FlowStep from "@/views/taskplatform/taskmanagement/flowStep.vue"; |
||||
|
import { constAiEffect } from "@/api/DesignForm/utils"; |
||||
|
|
||||
|
import RunFlowStep from "@/views/taskplatform/taskmanagement/runNewFlowStep.vue"; |
||||
|
import AiPage from "@/views/sysworkflow/lowcodepage/pageFlow/aiPage.vue"; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
isshow: { |
||||
|
type: Boolean, |
||||
|
default: true, |
||||
|
}, |
||||
|
drawerWith: { |
||||
|
type: Number, |
||||
|
default: 0, |
||||
|
}, |
||||
|
flowLogInfo: { |
||||
|
type: Object, |
||||
|
default() { |
||||
|
return {}; |
||||
|
}, |
||||
|
}, |
||||
|
operState: { |
||||
|
type: Number, |
||||
|
default: 1, |
||||
|
}, |
||||
|
}); |
||||
|
const flowAry = ref<any[]>(); |
||||
|
const emits = defineEmits(["update:isshow", "getmytodolist"]); |
||||
|
const isOpen = computed({ |
||||
|
get: () => props.isshow, |
||||
|
set: (val) => { |
||||
|
emits("update:isshow", val); |
||||
|
}, |
||||
|
}); |
||||
|
const drawBodyWidth = ref(props.drawerWith) |
||||
|
const aiConfigArea =ref(false) //是否有AI |
||||
|
const isFlowTable = ref(true); //判断是不是流程表单 |
||||
|
const formType = ref<number>(3); |
||||
|
const logistrue = ref(false); |
||||
|
const formLoading = ref(false); |
||||
|
const loadingData = ref(false); //加载表单数据 |
||||
|
const flowLoading = ref(false); //加载流程数据 |
||||
|
const versiontitle = ref<any>(""); |
||||
|
const flowMap = ref<any>(); |
||||
|
const nextStep = ref(0); |
||||
|
const nodeKey = ref<string>(""); |
||||
|
const purviewAry = ref<any[]>([]); |
||||
|
const currentProgress = ref<number>(1); |
||||
|
const newLogAry = ref<any>(""); |
||||
|
const oldLogAry = ref<any>(""); |
||||
|
const newDataLen = ref<number>(0); |
||||
|
//表单相关内容 |
||||
|
const formState = reactive({ |
||||
|
formData: { |
||||
|
list: [] as any[], |
||||
|
form: {}, |
||||
|
config: {}, |
||||
|
powerstr: {}, |
||||
|
aiConfig:[] |
||||
|
}, |
||||
|
dict: {}, |
||||
|
formId: 25, |
||||
|
id: 1, |
||||
|
loading: true, |
||||
|
}); |
||||
|
const formEl = ref<any>(); |
||||
|
const drawerBeforeClose = () => { |
||||
|
// console.log("监听打开关闭",false) |
||||
|
emits("getmytodolist"); |
||||
|
emits("update:isshow", false); |
||||
|
drawBodyWidth.value = 0; |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
versiontitle.value = props.flowLogInfo.title; |
||||
|
flowMap.value = JSON.parse(props.flowLogInfo.flowcont); |
||||
|
// console.log("监听打开关闭",props.flowLogInfo) |
||||
|
gainFormData(); |
||||
|
}); |
||||
|
const pageLog = ref<any[]>([]); |
||||
|
//获取表单内容 |
||||
|
const gainFormData = () => { |
||||
|
formLoading.value = true; |
||||
|
console.log("获取表单内容--->", props.flowLogInfo); |
||||
|
nextStep.value = props.flowLogInfo.nextStep; |
||||
|
nodeKey.value = props.flowLogInfo.currentNodeKey; |
||||
|
purviewAry.value = props.flowLogInfo.powerAry; |
||||
|
currentProgress.value = props.flowLogInfo.currentStep; |
||||
|
// console.log("props.flowLogInfo--5-------->",props.flowLogInfo.mastesform) |
||||
|
formState.formData = stringToObj(props.flowLogInfo.mastesform); |
||||
|
gainTaskFormInfo({ id: props.flowLogInfo.mastersKeyStr.toString(),runFlowId: props.flowLogInfo.runFlowIdStr.toString() }) |
||||
|
.then((data: any) => { |
||||
|
let extraW=0; |
||||
|
if(isFlowTable.value){ |
||||
|
extraW = 320 |
||||
|
} |
||||
|
if (data.data.logistrue) { |
||||
|
formState.formData.list.push(editLookFormCont); |
||||
|
} |
||||
|
// console.log("res----获得数据回显------->",data) |
||||
|
formEl.value.setValue(data.data.masterDataInfo); |
||||
|
newLogAry.value = data.data.newData; |
||||
|
oldLogAry.value = data.data.oldData; |
||||
|
logistrue.value = data.data.logistrue; |
||||
|
newDataLen.value = data.data.newDataLen; |
||||
|
formLoading.value = false; |
||||
|
|
||||
|
if(formState.formData&&formState.formData.aiConfig&&formState.formData.aiConfig.length>0){ |
||||
|
aiConfigArea.value=true |
||||
|
formState.formData.aiConfig?.forEach((item: { library: any; title: any; trigger: any; }) => { |
||||
|
currentAgent.value.push({ |
||||
|
name: "test", |
||||
|
model: false, |
||||
|
rowdex:0, |
||||
|
uuid: item.library, |
||||
|
fields: item.title, |
||||
|
trigger: item.trigger, |
||||
|
params: {}, |
||||
|
subparams:[] |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
if(aiConfigArea.value){ |
||||
|
extraW = extraW + 320 |
||||
|
} |
||||
|
|
||||
|
if (aiConfigArea.value && isFlowTable.value){ |
||||
|
drawBodyWidth.value = drawBodyWidth.value + extraW - 160 |
||||
|
}else if ((!aiConfigArea.value && isFlowTable.value) || (aiConfigArea.value && !isFlowTable.value)){ |
||||
|
drawBodyWidth.value = drawBodyWidth.value + extraW |
||||
|
}else{ |
||||
|
drawBodyWidth.value = drawBodyWidth.value |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
nextTick(() => { |
||||
|
// console.log("获取自定义表单内容-1111-->",data.data.tableData) |
||||
|
formEl.value.setValue(data.data.tableData); |
||||
|
}); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
formLoading.value = false; |
||||
|
gainEditDataLog({ id: props.flowLogInfo.mastersKeyStr.toString() }).then((data:any) => { |
||||
|
// console.log("获取修改记录-1111-->",data) |
||||
|
pageLog.value = data.data; |
||||
|
}); |
||||
|
}); |
||||
|
// haveCustomerFormVersion({id:props.flowLogInfo.version_id.toString()}) |
||||
|
// .then(({ data }) =>{ |
||||
|
|
||||
|
// formState.dict = string2json(data.dict) |
||||
|
// judgeSubmitCancel({"name":data.mastesformjson}) |
||||
|
// .then((dataBut:any) =>{ |
||||
|
// if(dataBut.code == 0){ |
||||
|
// formState.formData.list.push(afreshSubmitButton) |
||||
|
// // console.log("res----判断按钮------->",dataBut,props.operState) |
||||
|
// // if (dataBut.data == 3 || dataBut.data == 4){ |
||||
|
// // formInfo.formData.list.push(editFormCont) |
||||
|
// // } |
||||
|
// } |
||||
|
// }) |
||||
|
// }) |
||||
|
// .finally(() => { |
||||
|
// formLoading.value = false; |
||||
|
|
||||
|
// formEl |
||||
|
// }) |
||||
|
}; |
||||
|
//-----------------------AI setting-------------------------- |
||||
|
//当前AI智能体,根据form表单的配置来初始化 |
||||
|
//params 主表字段参数 subparams子表字段参数,子表触发时需要清空 ; rowdex:表示子表的第几条数据,没有的话说明不是子表数据 |
||||
|
const currentAgent = ref< |
||||
|
{ |
||||
|
model: boolean; |
||||
|
name: string; |
||||
|
rowdex:number, |
||||
|
uuid: string[]; |
||||
|
fields: string[]; |
||||
|
trigger: number; |
||||
|
params: { [key: string]: any }; |
||||
|
subparams:{[key: string]: any}[]; |
||||
|
}[] |
||||
|
>([]); |
||||
|
const aiassistRef = ref(); |
||||
|
//触发AI查询事件 |
||||
|
provide(constAiEffect, ({ key, value, field,rowdex}: any) => { |
||||
|
//新建ai_envents 是为了解决前端并发问题 |
||||
|
const ai_events: Array<{ uuids: string[]; params: { [key: string]: any } }> = []; |
||||
|
currentAgent.value.forEach(ag=>{ |
||||
|
if(ag.fields.includes(key)){ //trigger: 1:任一 2:半数 3:全部 |
||||
|
if (rowdex!=null){//子表操作 |
||||
|
ag.rowdex=rowdex //更新智能体的rowdex |
||||
|
if(rowdex>=ag.subparams.length){ |
||||
|
ag.subparams.push({[field]:value}) |
||||
|
}else{ |
||||
|
ag.subparams[rowdex][field]=value |
||||
|
} |
||||
|
}else{ //主表操作, 直接加入params种 |
||||
|
ag.params[field]=value; //这个地方把key换成fieldName |
||||
|
} |
||||
|
let mergedObj; |
||||
|
if(ag.subparams.length>0){ |
||||
|
mergedObj = Object.assign({}, ag.params, ag.subparams[ag.rowdex]); //合并子表和主表 |
||||
|
}else{ |
||||
|
mergedObj = ag.params |
||||
|
} |
||||
|
switch(ag.trigger){ |
||||
|
case 2: |
||||
|
if(Object.keys(mergedObj).length>=ag.fields.length/2){ |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj }) |
||||
|
} |
||||
|
break; |
||||
|
case 3: |
||||
|
if(Object.keys(mergedObj).length==ag.fields.length){ |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj}) |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
ai_events.push({uuids:ag.uuid,params:mergedObj}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
if(ai_events.length>0){ |
||||
|
aiassistRef.value.onSendParamToAI(ai_events) |
||||
|
} |
||||
|
}) |
||||
|
const beforeSubmit = () => { |
||||
|
|
||||
|
} |
||||
|
const afterSubmit = () => { |
||||
|
|
||||
|
} |
||||
|
const closeAppSubmit = () => { |
||||
|
|
||||
|
} |
||||
|
const changeKeyVal = () => { |
||||
|
|
||||
|
} |
||||
|
const anewSubmit = () => { |
||||
|
|
||||
|
} |
||||
|
const saveEditFormInfo = () => { |
||||
|
|
||||
|
} |
||||
|
const sendDraftSubmit = () => { |
||||
|
|
||||
|
} |
||||
|
const optionsValue3Get3 = () => { |
||||
|
|
||||
|
} |
||||
|
const submitEdit = () => { |
||||
|
|
||||
|
} |
||||
|
</script> |
||||
|
<template> |
||||
|
<div v-if="isOpen" class="drawerClass"> |
||||
|
<el-drawer |
||||
|
v-model="isOpen" |
||||
|
:title="versiontitle" |
||||
|
:close-on-click-modal="false" |
||||
|
:close-on-press-escape="false" |
||||
|
:destroy-on-close="true" |
||||
|
:size="drawBodyWidth" |
||||
|
:close="drawerBeforeClose" |
||||
|
> |
||||
|
<template #header> |
||||
|
<div class="drawHeader"> |
||||
|
<el-text size="large">{{ versiontitle }}</el-text> |
||||
|
</div> |
||||
|
</template> |
||||
|
<div v-loading="loadingData" :class="aiConfigArea&&isFlowTable?'drawBody treePage':(aiConfigArea&&!isFlowTable)?'drawBody twoPageAi':(!aiConfigArea&&isFlowTable)?'drawBody twoPageFlow':'drawBody'"> |
||||
|
<!-- {{ formState.formData }} --> |
||||
|
<AiPage |
||||
|
v-if="formType != 5 && aiConfigArea" |
||||
|
ref="aiassistRef" |
||||
|
:agent="currentAgent" |
||||
|
/> |
||||
|
<AppForm |
||||
|
ref="formEl" |
||||
|
:draw-title="versiontitle" |
||||
|
:form-data="formState.formData" |
||||
|
|
||||
|
:type="formType" |
||||
|
:dict="formState.dict" |
||||
|
request-url="getFormContent" |
||||
|
add-url="saveFormContent" |
||||
|
edit-url="editFormContent" |
||||
|
:before-submit="beforeSubmit" |
||||
|
:after-submit="afterSubmit" |
||||
|
:close-app-submit="closeAppSubmit" |
||||
|
:change-key-val="changeKeyVal" |
||||
|
:anew-submit="anewSubmit" |
||||
|
:save-edit-form-info="saveEditFormInfo" |
||||
|
:send-draft-submit="sendDraftSubmit" |
||||
|
:submit-edit="submitEdit" |
||||
|
@options-value3-get3="optionsValue3Get3" |
||||
|
/> |
||||
|
|
||||
|
<RunFlowStep |
||||
|
v-if="isFlowTable" |
||||
|
ref="runstep" |
||||
|
v-model:flowary="flowAry" |
||||
|
:flow-key="props.flowLogInfo.idStr" |
||||
|
:current-progress="currentProgress" |
||||
|
@updatelist="drawerBeforeClose" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
</el-drawer> |
||||
|
</div> |
||||
|
</template> |
||||
|
<style lang='scss' scoped> |
||||
|
.drawerClass{ |
||||
|
:deep .el-drawer__header{ |
||||
|
border-bottom: 1px solid #ECECEC; |
||||
|
} |
||||
|
} |
||||
|
.drawHeader { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.drawBody{ |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
padding: 10px 10px; |
||||
|
:deep .el-card{ |
||||
|
border-radius: 15px; |
||||
|
} |
||||
|
:deep .el-card__header{ |
||||
|
padding: 15px 15px; |
||||
|
} |
||||
|
:deep .el-card__body{ |
||||
|
padding: 0; |
||||
|
} |
||||
|
:deep .el-card__footer{ |
||||
|
padding: 10px 10px; |
||||
|
} |
||||
|
} |
||||
|
.twoPageAi{ |
||||
|
display: grid; |
||||
|
grid-template-columns: minmax(150px, 250px) 1fr; /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.twoPageFlow{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr minmax(150px, 250px); /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.treePage{ |
||||
|
display: grid; |
||||
|
grid-template-columns: minmax(150px, 250px) 1fr minmax(150px, 250px); /* 左右最小150px,最大250px,中间自适应 */ |
||||
|
grid-template-rows: auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
.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; |
||||
|
} |
||||
|
.card-header{ |
||||
|
font-size: 1.4rem; |
||||
|
font-weight: 700; |
||||
|
color: #0020C2; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
.scroBox{ |
||||
|
padding: 10px 15px; |
||||
|
height: calc(100vh - 200px); |
||||
|
} |
||||
|
.formBody{ |
||||
|
width: 100%; |
||||
|
overflow-x: auto; |
||||
|
} |
||||
|
.flowBody{ |
||||
|
padding: 10px 15px; |
||||
|
height: calc(100vh - 260px); |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.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); |
||||
|
} |
||||
|
.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); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,465 @@ |
|||||
|
<!-- |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2026-02-02 14:00:47 |
||||
|
@ 备注: 新流程 |
||||
|
--> |
||||
|
<script lang='ts' setup> |
||||
|
import { gainRunTaskFlow } from "@/api/DesignForm/requestapi"; |
||||
|
import SvgIcon from "@/components/SvgIcon/index.vue"; |
||||
|
|
||||
|
//引入图标 |
||||
|
import squareUrlOne from "@/assets/images/1.png" |
||||
|
import squareUrlTwo from "@/assets/images/2.png" |
||||
|
import { afreshRunWorkflow, authorizeWorkflow } from "@/api/taskapi/management"; |
||||
|
|
||||
|
const state = reactive({ |
||||
|
circleUrl:squareUrlTwo, |
||||
|
squareUrl: squareUrlOne, |
||||
|
sizeList: ['small', '', 'large'] as const, |
||||
|
}) |
||||
|
const { circleUrl, squareUrl, sizeList } = toRefs(state) |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
flowKey:{ |
||||
|
type:String, |
||||
|
default:"" |
||||
|
}, |
||||
|
currentProgress:{ |
||||
|
type:Number, |
||||
|
default:0 |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const emits = defineEmits(["update:flowary","updatelist"]); |
||||
|
const ifSendFlow = ref(false)//是否可以写评论 |
||||
|
const flowLoading = ref(false) |
||||
|
const flowMaps = ref<any[]>(); |
||||
|
// const flowOpinion = ref(false) //是否可以写评论 |
||||
|
const currentStep = ref<number>(props.currentProgress) |
||||
|
const nextStep = ref<number>(0) |
||||
|
|
||||
|
//获取任务流程 |
||||
|
const gainRunFlowTask = () =>{ |
||||
|
flowLoading.value = true |
||||
|
let sendInfo = { |
||||
|
id:props.flowKey |
||||
|
} |
||||
|
gainRunTaskFlow(sendInfo) |
||||
|
.then((data:any) =>{ |
||||
|
console.log("获取流程--werwerwerwer--->",data) |
||||
|
flowMaps.value = data.data.flowList |
||||
|
ifSendFlow.value = data.data.operational |
||||
|
currentStep.value = data.data.current_step |
||||
|
nextStep.value = data.data.next_step |
||||
|
emits("update:flowary", data.data.flowList); |
||||
|
}) |
||||
|
.finally(()=>{ |
||||
|
flowLoading.value = false |
||||
|
}) |
||||
|
} |
||||
|
/** |
||||
|
@ 作者: 秦东 |
||||
|
@ 时间: 2026-01-29 16:37:56 |
||||
|
@ 功能: 判断审批节点样式 |
||||
|
*/ |
||||
|
const judgeNodeClass = (val:number,stepVal:number):string => { |
||||
|
console.log("判断审批节点样式",val,stepVal) |
||||
|
switch(val){ |
||||
|
case 1: |
||||
|
if(ifSendFlow.value){ |
||||
|
if(currentStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'completed' |
||||
|
} |
||||
|
}else{ |
||||
|
if(nextStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'completed' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
case 2: |
||||
|
return 'pending' |
||||
|
case 3: |
||||
|
// return 'in-progress' |
||||
|
if(ifSendFlow.value){ |
||||
|
if(currentStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'in-executor' |
||||
|
} |
||||
|
}else{ |
||||
|
if(nextStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'completed' |
||||
|
} |
||||
|
} |
||||
|
default: |
||||
|
if(ifSendFlow.value){ |
||||
|
if(currentStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'completed' |
||||
|
} |
||||
|
}else{ |
||||
|
if(nextStep.value == stepVal){ |
||||
|
return "in-progress" |
||||
|
}else{ |
||||
|
return 'completed' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
return 'completed' |
||||
|
} |
||||
|
|
||||
|
//组件渲染之前 |
||||
|
onBeforeMount(()=>{ |
||||
|
gainRunFlowTask(); |
||||
|
}) |
||||
|
//判断是否可以添加人员 |
||||
|
const judgeAddUser = (val:any):boolean =>{ |
||||
|
// console.log("获取流程----1111->",val.judgelist) |
||||
|
if(val.judgelist){ |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
let zhiXingStep = 1; |
||||
|
const presetPersonnel = ref<any>([]); //预设选择人 |
||||
|
const selectedPeople = ref<any>([]); //已选择人 |
||||
|
const openclosebox = ref(false) |
||||
|
const openOrClose = ref(false) |
||||
|
const sendFlowInfo = ref<string>() //审批意见 |
||||
|
//添加操作人 |
||||
|
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) =>{ |
||||
|
|
||||
|
if(flowMaps.value&& flowMaps.value.length > 0){ |
||||
|
|
||||
|
flowMaps.value.forEach((item:any) =>{ |
||||
|
if(item.step == zhiXingStep){ |
||||
|
item.operator = val |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
//提交审批 |
||||
|
const yesOrNo = (val:string,agreeOrRefuse:number) =>{ |
||||
|
// console.log("提交审批----1111->",val,agreeOrRefuse,sendFlowInfo.value) |
||||
|
let sendInfo = { |
||||
|
id:props.flowKey, |
||||
|
agreeOrRefuse:agreeOrRefuse, |
||||
|
suggest:sendFlowInfo.value, |
||||
|
flowlist:flowMaps.value |
||||
|
} |
||||
|
authorizeWorkflow(sendInfo) |
||||
|
.then((data:any)=>{ |
||||
|
|
||||
|
// console.log("提交审批----22222->",data) |
||||
|
ElMessage({ |
||||
|
message: '处理完成!', |
||||
|
type: 'success' |
||||
|
}) |
||||
|
|
||||
|
}) |
||||
|
.finally(()=>{ |
||||
|
gainRunFlowTask(); |
||||
|
emits("updatelist"); |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
//重新提交审核 |
||||
|
const anewSubmit = (type: string,val?:any) => { |
||||
|
|
||||
|
afreshRunWorkflow({id:props.flowKey}) |
||||
|
.then((data:any) => { |
||||
|
|
||||
|
}) |
||||
|
.finally(() => { |
||||
|
gainRunFlowTask() |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
defineExpose({gainRunFlowTask,anewSubmit}) |
||||
|
</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 v-loading="flowLoading" element-loading-text="Loading..." :class="ifSendFlow?'flowBody':'flowBodyNofoot'"> |
||||
|
<div class="approval-steps"> |
||||
|
<div v-for="item in flowMaps" :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 v-if="ifSendFlow" #footer> |
||||
|
<div class="bootemWorkFlow"> |
||||
|
<el-input v-model="sendFlowInfo" type="textarea" :rows="2" style="width: 100%" placeholder="请输入审批意见"></el-input> |
||||
|
<div class="bootemWorkFlowBut"> |
||||
|
<el-button class="btn approve-btn" @click="yesOrNo(props.flowKey,1)"><SvgIcon icon-class="kxdg" size="" style="margin-right: 5px" />通过审批</el-button> |
||||
|
<el-button class="btn reject-btn" @click="yesOrNo(props.flowKey,2)" ><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> |
||||