联调 退役管理

This commit is contained in:
GGJ
2024-04-18 14:54:35 +08:00
parent 02ad259e30
commit 46ee3336c4
13 changed files with 974 additions and 177 deletions

View File

@@ -50,7 +50,7 @@ export const updateModelState = async (id: number, state: number) => {
return await createAxios({ url: '/process-boot/bpm/model/update-state', method: 'put', data: data }) return await createAxios({ url: '/process-boot/bpm/model/update-state', method: 'put', data: data })
} }
export const save = async data => { export const save = async (data: any) => {
return await createAxios({ url: '/process-boot/flowable/definition/save', method: 'POST', data: data }) return await createAxios({ url: '/process-boot/flowable/definition/save', method: 'POST', data: data })
} }
@@ -70,3 +70,9 @@ export const assFormWithDeploy = async (data: any) => {
export const deleteDeploy = async (data: any) => { export const deleteDeploy = async (data: any) => {
return createAxios({ url: 'process-boot/flowable/definition/delete', method: 'POST', data }) return createAxios({ url: 'process-boot/flowable/definition/delete', method: 'POST', data })
} }
export const flowRecord = async (data: any) => {
return createAxios({ url: '/process-boot/flowable/task/flowRecord', method: 'GET', params: data })
}
export const flowXmlAndNode = async (data: any) => {
return createAxios({ url: '/process-boot/flowable/task/flowXmlAndNode', method: 'GET', params: data })
}

View File

@@ -0,0 +1,41 @@
import createAxios from '@/utils/request'
//上传文件
export function uploadFile(data: any) {
return createAxios({
url: '/process-boot/rFlowProcess/uploadFile',
method: 'post',
data
})
}
//新增申请单
export function addFlow(data: any) {
return createAxios({
url: '/process-boot/rFlowProcess/add',
method: 'POST',
data
})
}
//更新申请单
export function update(data: any) {
return createAxios({
url: '/process-boot/rFlowProcess/update',
method: 'POST',
data
})
}
//提交申请单审核
export function createCheckflow(data: any) {
return createAxios({
url: '/process-boot/rFlowProcess/createCheckflow',
method: 'POST',
params: data
})
}
//提交申请单审核
export function getFileUrl(data: any) {
return createAxios({
url: '/process-boot/rFlowProcess/getFileUrl',
method: 'POST',
params: data
})
}

View File

@@ -1,6 +1,6 @@
import MyProcessDesigner from './designer' import MyProcessDesigner from './designer'
import MyProcessPenal from './penal' import MyProcessPenal from './penal'
// import MyProcessViewer from './designer/index2' import MyProcessViewer from './designer/index2'
import './theme/index.scss' import './theme/index.scss'
import 'bpmn-js/dist/assets/diagram-js.css' import 'bpmn-js/dist/assets/diagram-js.css'
@@ -8,4 +8,4 @@ import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css' import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css' import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
export { MyProcessDesigner,MyProcessPenal } export { MyProcessDesigner, MyProcessPenal,MyProcessViewer }

View File

@@ -26,6 +26,7 @@
</el-table> </el-table>
<el-dialog <el-dialog
draggable
v-model="dialogVisible" v-model="dialogVisible"
:title="modelConfig.title" :title="modelConfig.title"
:close-on-click-modal="false" :close-on-click-modal="false"

View File

@@ -21,8 +21,8 @@
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="审核状态"> <el-form-item label="状态">
<el-select v-model="tableStore.table.params.reportProcessStatus" clearable placeholder="请选择审核状态"> <el-select v-model="tableStore.table.params.reportProcessStatus" clearable placeholder="请选择状态">
<el-option <el-option
v-for="item in auditStatus" v-for="item in auditStatus"
:key="item.code" :key="item.code"
@@ -113,7 +113,7 @@ const tableStore: any = new TableStore({
}, },
{ {
field: 'reportProcessStatus', field: 'reportProcessStatus',
title: '审核状态', title: '状态',
formatter: (row: any) => { formatter: (row: any) => {
return auditStatus.filter(item => item.code == row.cellValue)[0]?.name return auditStatus.filter(item => item.code == row.cellValue)[0]?.name
} }

View File

@@ -1,22 +1,21 @@
<template> <template>
<el-dialog draggable title="新增设备退役申请单" v-model="userAdd" width="500px" :before-close="cancel"> <el-dialog draggable :title="title" v-model="userAdd" width="500px" :before-close="cancel">
<el-divider content-position="left" style="font-weight: bolder; font-size: 18px">基本信息</el-divider> <el-divider content-position="left" style="font-weight: bolder; font-size: 18px">基本信息</el-divider>
<el-form :inline="true" ref="formRef" :model="addData" label-width="auto" class="form-one" :rules="rules"> <el-form :inline="true" ref="formRef" :model="addData" label-width="auto" class="form-one" :rules="rules">
<el-form-item label="区域:"> <el-form-item label="区域:">
<Area ref="areaRef" v-model="addData.orgNo" /> <Area ref="areaRef" v-model="addData.orgNo" />
</el-form-item> </el-form-item>
<el-form-item label="设备编号:" prop="userName"> <el-form-item label="设备编号:" prop="assetNumber">
<el-input v-model="addData.userName" clearable placeholder="请输入关键字"></el-input> <el-input v-model="addData.assetNumber" clearable placeholder="请输入关键字"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="资产编号:" prop="userName"> <el-form-item label="资产编号:" prop="devNumber">
<el-input v-model="addData.userName" clearable placeholder="请输入关键字"></el-input> <el-input v-model="addData.devNumber" clearable placeholder="请输入关键字"></el-input>
</el-form-item> </el-form-item>
<el-form-item class="item" label="报告:" style="margin-top: 10px" prop="fileList"> <el-form-item class="item" label="报告:" style="margin-top: 10px" prop="fileList">
<el-upload <el-upload
v-model:file-list="causeAnalysisData.fileList" v-model:file-list="addData.fileList"
ref="upload" ref="upload"
v-if="!prop.disabled"
action="" action=""
:limit="1" :limit="1"
:on-exceed="handleExceed" :on-exceed="handleExceed"
@@ -26,111 +25,124 @@
<el-button type="primary">上传文件</el-button> <el-button type="primary">上传文件</el-button>
</template> </template>
</el-upload> </el-upload>
<el-button type="primary" link @click="download" v-else>
{{ prop.List.fileNameYyfx }}
</el-button>
</el-form-item> </el-form-item>
<el-form-item label="选择审核人:" > <el-form-item label="选择审核人:" prop="checker">
<el-select v-model="addData.checker" clearable placeholder="请选择审核人">
<el-select v-model="auditUser" clearable placeholder="请选择审核人"> <el-option v-for="item in auditList" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-option v-for="item in auditList" :key="item.id" :label="item.name" :value="item.id"></el-option> </el-select>
</el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div style="display: flex; justify-content: center; margin-top: 30px"> <div style="display: flex; justify-content: center; margin-top: 30px">
<el-button type="primary" class="ml20" @click="config">确定</el-button> <el-button type="primary" class="ml20" @click="config(1)">保存</el-button>
<el-button type="primary" class="ml20" @click="cancel">取消</el-button> <!-- <el-button type="primary" class="ml20" @click="config(2)">审核</el-button> -->
<el-button class="ml20" @click="cancel">取消</el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { ElMessage, genFileId } from 'element-plus'
import Area from '@/components/form/area/index.vue' import Area from '@/components/form/area/index.vue'
import { addLoadTypeUser } from '@/api/process-boot/interference' import { uploadFile, addFlow, update } from '@/api/process-boot/retire'
import { onMounted } from 'vue'
import { getUserByRoleType } from '@/api/user-boot/user' import { getUserByRoleType } from '@/api/user-boot/user'
import { UploadProps, UploadRawFile } from 'element-plus' import { UploadProps, UploadRawFile, genFileId, ElMessage } from 'element-plus'
const dictData = useDictData() const dictData = useDictData()
const userAdd = ref(false) const userAdd = ref(false)
const interferenceType = dictData.getBasicData('Interference_Source') const interferenceType = dictData.getBasicData('Interference_Source')
const emit = defineEmits(['onSubmit']) const emit = defineEmits(['onSubmit'])
const rules = reactive({ const rules = reactive({
loadType: [{ required: true, message: '请选择干扰源类型', trigger: 'change' }], assetNumber: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
userName: [{ required: true, message: '请输入干扰源用户名称', trigger: 'blur' }], devNumber: [{ required: true, message: '请输入资产编号', trigger: 'blur' }],
recordTime: [{ required: true, message: '请选择建档时间', trigger: 'change' }]
checker: [{ required: true, message: '请选择审核人', trigger: 'change' }],
fileList: [{ required: true, message: '请选择文件', trigger: 'change' }]
}) })
const addData = ref({ const auditList: any = ref([])
const title: any = ref('')
const addData: any = ref({
orgNo: dictData.state.area[0].id, orgNo: dictData.state.area[0].id,
loadType: '', assetNumber: '',
userName: '', devNumber: '',
recordTime: '' fileList: [],
checker: '',
applyType: 4,
type: 5
}) })
const formRef = ref() const formRef = ref()
// 新增 // 新增
const config = () => { const config = (num?: number) => {
formRef.value.validate((valid: any) => { formRef.value.validate(async (valid: any) => {
if (valid) { if (valid) {
addLoadTypeUser(addData.value).then((res: any) => { if (title.value == '新增设备退役申请单') {
ElMessage.success('新增成功!') let form = new FormData()
emit('onSubmit') form.append('file', addData.value.fileList[0].raw)
cancel() await uploadFile(form).then((res: any) => {
}) addData.value.fileName = res.data.minFileName
addData.value.filePath = res.data.minFileUrl
})
addData.value.applicationFormText = JSON.stringify({
assetNumber: addData.value.assetNumber,
devNumber: addData.value.devNumber
})
addData.value.operate = num
addData.value.checkerName = auditList.value.find((item: any) => item.id == addData.value.checker).name
addFlow(addData.value).then((res: any) => {
ElMessage.success('新增成功!')
cancel()
})
} else if (title.value == '编辑设备退役申请单') {
if (addData.value.fileList[0].raw != undefined) {
let form = new FormData()
form.append('file', addData.value.fileList[0].raw)
await uploadFile(form).then((res: any) => {
addData.value.fileName = res.data.minFileName
addData.value.filePath = res.data.minFileUrl
})
}
addData.value.applicationFormText = JSON.stringify({
assetNumber: addData.value.assetNumber,
devNumber: addData.value.devNumber
})
addData.value.operate = num
addData.value.checkerName = auditList.value.find((item: any) => item.id == addData.value.checker).name
update(addData.value).then((res: any) => {
ElMessage.success('修改成功!')
cancel()
})
}
} }
}) })
} }
// 重置 // 重置
const cancel = () => { const cancel = () => {
formRef.value.resetFields() formRef.value.resetFields()
emit('onSubmit')
userAdd.value = false userAdd.value = false
} }
const open = () => { const open = (text: string, row?: any) => {
if (row) {
addData.value = JSON.parse(JSON.stringify(row))
addData.value.assetNumber = row.applyForm.assetNumber
addData.value.devNumber = row.applyForm.devNumber
addData.value.fileList = [{ name: row.fileName, url: row.filePath }]
}
title.value = text
userAdd.value = true userAdd.value = true
} }
const auditList:any = ref([])
const auditUser = ref('')
const handleClose = () => {
auditUser.value = ''
}
// 取消 // 取消
onMounted(() => { onMounted(() => {
getUserByRoleType(3).then(res => { getUserByRoleType(3).then(res => {
auditList.value = res.data auditList.value = res.data
}) })
}) })
const prop = defineProps({
addData: {
type: Object,
default: () => {}
},
List: {
type: Object,
default: () => {}
},
disabled: {
type: Boolean
}
})
const upload = ref() const upload = ref()
const causeAnalysisData: any = ref({
reportProcessContentYyfx: [],
userReportProcessContentYyfx: [],
eventDescriptionYyfx: '',
fileNameYyfx: '', //原因分析报告文件名称
filePathYyfx: '', //原因分析报告文件路径
powerGridAffectDev: [],
userAffectDev: [],
fileList: []
})
// 上传 // 上传
const handleExceed: UploadProps['onExceed'] = files => { const handleExceed: UploadProps['onExceed'] = files => {
upload.value!.clearFiles() upload.value!.clearFiles()
@@ -138,18 +150,11 @@ const handleExceed: UploadProps['onExceed'] = files => {
file.uid = genFileId() file.uid = genFileId()
upload.value!.handleStart(file) upload.value!.handleStart(file)
} }
// 下载
const download = async () => {
// window.open(addForm.value.ifile)
let response = await fetch(prop.List.filePathYyfx)
let blob = await response.blob()
let a = document.createElement('a')
a.href = window.URL.createObjectURL(blob)
a.download = prop.List.fileNameYyfx
a.click()
a.remove()
}
defineExpose({ open }) defineExpose({ open })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
:deep(.el-upload-list__item) {
width: 300px;
}
</style>

View File

@@ -8,7 +8,7 @@
<Table ref="tableRef" /> <Table ref="tableRef" />
</div> </div>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<Equipment ref="EquipmentRef" /> <Equipment ref="EquipmentRef" @onSubmit="tableStore.index()" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide, nextTick } from 'vue' import { ref, onMounted, provide, nextTick } from 'vue'
@@ -18,18 +18,19 @@ import TableHeader from '@/components/table/header/index.vue'
import Equipment from './equipment.vue' import Equipment from './equipment.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { deleteIssues } from '@/api/process-boot/electricitymanagement' import { deleteIssues } from '@/api/process-boot/electricitymanagement'
import { createCheckflow, getFileUrl } from '@/api/process-boot/retire'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
const dictData = useDictData() const dictData = useDictData()
const TableHeaderRef = ref() const TableHeaderRef = ref()
const EquipmentRef = ref() const EquipmentRef = ref()
const problemData = dictData.getBasicData('Problem_Sources') const problemData = dictData.getBasicData('Problem_Sources')
const fillingProgress = dictData.getBasicData('Fill_Progress')
const auditStatus = dictData.getBasicData('Audit_Status')
defineOptions({ defineOptions({
name: '/Processsupervision/retire' name: '/Processsupervision/retire'
}) })
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/process-boot/electricityQuality/getIssues', url: '/process-boot/rFlowProcess/getFlowProcess',
method: 'POST', method: 'POST',
publicHeight: 65, publicHeight: 65,
column: [ column: [
@@ -44,68 +45,103 @@ const tableStore: any = new TableStore({
}, },
{ field: 'orgName', title: '所属单位' }, { field: 'orgName', title: '所属单位' },
{ {
field: 'problemSources', field: 'applyForm',
title: '问题来源' title: '设备编号',
formatter: (row: any) => {
return row.cellValue.assetNumber
}
}, },
{ field: 'powerQualityProblemNo', title: '问题编号' }, {
{ field: 'problemName', title: '问题名称' }, field: 'applyForm',
{ field: 'dataDate', title: '问题新建时间' }, title: '资产编号',
formatter: (row: any) => {
return row.cellValue.devNumber
}
},
{ field: 'updateTime', title: '创建时间' },
{ field: 'checkerName', title: '审核人' },
{
field: 'processStatus',
title: '流程状态',
width: 150,
render: 'tag',
custom: {
0: '',
1: '',
2: 'warning',
3: 'danger',
4: 'success',
5: 'success'
},
replaceValue: {
0: '',
1: '新建',
2: '待审核',
3: '驳回',
4: '已审核',
5: '归档'
}
},
{ {
title: '操作', title: '操作',
width: '180', width: '200',
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
{ {
name: 'edit', name: 'edit',
title: '查看', title: '编辑',
type: 'primary', type: 'primary',
disabled: row => { disabled: row => {
return row.reportProcessStatus == 'Init' return !(row.processStatus == 1 || row.processStatus == 3)
}, },
icon: 'el-icon-Plus', icon: 'el-icon-Plus',
render: 'basicButton', render: 'basicButton',
click: async row => {} click: async row => {
EquipmentRef.value.open('编辑设备退役申请单', row)
}
}, },
{
name: 'edit',
title: '填报',
disabled: row => {
return (
row.reportProcessStatus == 'Auditt' ||
(row.reportProcess == 'Insights' && row.reportProcessStatus == 'Success') ||
row.reportProcess == 'Archived'
)
},
type: 'primary',
icon: 'el-icon-Plus',
render: 'basicButton',
click: row => {}
},
{
name: 'edit',
title: '归档',
{
name: 'edit',
title: '提交审核',
disabled: row => {
return !(row.processStatus == 1 || row.processStatus == 3)
},
type: 'primary', type: 'primary',
icon: 'el-icon-SuccessFilled', icon: 'el-icon-Plus',
render: 'basicButton', render: 'confirmButton',
popconfirm: { popconfirm: {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
confirmButtonType: 'danger', title: '是否提交审核?'
title: '确定归档?'
}, },
click: row => {} click: row => {
createCheckflow({ id: row.id }).then(() => {
ElMessage.success('提交成功!')
tableStore.index()
})
}
}, },
{ {
name: 'edit', name: 'edit',
title: '审核记录', title: '下载文件',
type: 'primary', type: 'primary',
icon: 'el-icon-Plus',
icon: 'el-icon-PieChart',
render: 'basicButton', render: 'basicButton',
click: row => {} click: async row => {
getFileUrl({ filePath: row.filePath }).then(async res => {
let response = await fetch(res.data)
let blob = await response.blob()
let a = document.createElement('a')
a.href = window.URL.createObjectURL(blob)
a.download = row.fileName
a.click()
a.remove()
})
}
}, },
{ {
name: 'del', name: 'del',
text: '删除', text: '删除',
@@ -131,8 +167,9 @@ const tableStore: any = new TableStore({
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.orgNo = tableStore.table.params.deptIndex tableStore.table.params.orgNo = tableStore.table.params.deptIndex
tableStore.table.params.dataDate = tableStore.table.params.searchBeginTime tableStore.table.params.pageType = '1'
tableStore.table.params.dataType = TableHeaderRef.value.datePickerRef.interval tableStore.table.params.type = 5
tableStore.table.params.applyType = 4
} }
}) })

View File

@@ -0,0 +1,534 @@
<template>
<div class="my-process-designer">
<div class="my-process-designer__container">
<div class="my-process-designer__canvas" style="height: 760px" ref="bpmnCanvas"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onBeforeUnmount, onMounted, nextTick } from 'vue'
import BpmnViewer from 'bpmn-js/lib/Viewer'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
defineOptions({ name: 'MyProcessViewer' })
const props = defineProps({
value: {
// BPMN XML 字符串
type: String,
default: ''
},
prefix: {
// 使用哪个引擎
type: String,
default: 'camunda'
},
activityData: {
// 活动的数据。传递时,可高亮流程
type: Array,
default: () => []
}
})
const emit = defineEmits(['destroy'])
let bpmnModeler: any
const xml = ref('')
const activityLists = ref<any[]>([])
const processInstance = ref<any>(undefined)
const taskList = ref<any[]>([])
const bpmnCanvas = ref()
const initBpmnModeler = () => {
if (bpmnModeler) return
bpmnModeler = new BpmnViewer({
container: bpmnCanvas.value,
bpmnRenderer: {}
})
}
/* 创建新的流程图 */
const createNewDiagram = async (xml: any) => {
// 将字符串转换成图显示出来
let xmlString = xml
try {
let { warnings } = await bpmnModeler.importXML(xmlString)
if (warnings && warnings.length) {
// warnings.forEach(warn => console.warn(warn))
}
// 高亮流程图
await highlightDiagram()
const canvas = bpmnModeler.get('canvas')
canvas.zoom('fit-viewport', 'auto')
} catch (e) {
console.error(e)
// console.error(`[Process Designer Warn]: ${e?.message || e}`);
}
}
/* 高亮流程图 */
// TODO 芋艿:如果多个 endActivity 的话目前的逻辑可能有一定的问题。https://www.jdon.com/workflow/multi-events.html
const highlightDiagram = async () => {
const activityList = activityLists.value
if (activityList.length === 0) {
return
}
// 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
// 再次基础上,增加不同审批结果的颜色等等
let canvas = bpmnModeler.get('canvas')
let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务
let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务
let findProcessTask = false //是否已经高亮了进行中的任务
//进行中高亮之后的任务 key 集合,用于过滤掉 taskList 进行中后面的任务,避免进行中后面的数据 Hover 还有数据
let removeTaskDefinitionKeyList: any = []
// debugger
bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {
let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动
if (!activity) {
return
}
if (n.$type === 'bpmn:UserTask') {
// 用户任务
// 处理用户任务的高亮
const task: any = taskList.value.find((m: any) => m.id === activity.taskId) // 找到活动对应的 taskId
if (!task) {
return
}
// 进行中的任务已经高亮过了,则不高亮后面的任务了
if (findProcessTask) {
removeTaskDefinitionKeyList.push(n.id)
return
}
// 高亮任务
canvas.addMarker(n.id, getResultCss(task.status))
//标记是否高亮了进行中任务
if (task.status === 1) {
findProcessTask = true
}
// 如果非通过,就不走后面的线条了
if (task.status !== 2) {
return
}
// 处理 outgoing 出线
const outgoing = getActivityOutgoing(activity)
outgoing?.forEach((nn: any) => {
// debugger
let targetActivity: any = activityList.find((m: any) => m.key === nn.targetRef.id)
// 如果目标活动存在则根据该活动是否结束进行【bpmn:SequenceFlow】连线的高亮设置
if (targetActivity) {
canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')
} else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
// TODO 芋艿:这个流程,暂时没走到过
canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')
} else if (nn.targetRef.$type === 'bpmn:EndEvent') {
// TODO 芋艿:这个流程,暂时没走到过
if (!todoActivity && endActivity.key === n.id) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(nn.targetRef.id, 'highlight')
}
if (!activity.endTime) {
canvas.addMarker(nn.id, 'highlight-todo')
canvas.addMarker(nn.targetRef.id, 'highlight-todo')
}
}
})
} else if (n.$type === 'bpmn:ExclusiveGateway') {
// 排它网关
// 设置【bpmn:ExclusiveGateway】排它网关的高亮
canvas.addMarker(n.id, getActivityHighlightCss(activity))
// 查找需要高亮的连线
let matchNN: any = undefined
let matchActivity: any = undefined
n.outgoing?.forEach((nn: any) => {
let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
if (!targetActivity) {
return
}
// 特殊判断 endEvent 类型的原因ExclusiveGateway 可能后续连有 2 个路径:
// 1. 一个是 UserTask => EndEvent
// 2. 一个是 EndEvent
// 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。
// 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~
if (!matchActivity || matchActivity.type === 'endEvent') {
matchNN = nn
matchActivity = targetActivity
}
})
if (matchNN && matchActivity) {
canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))
}
} else if (n.$type === 'bpmn:ParallelGateway') {
// 并行网关
// 设置【bpmn:ParallelGateway】并行网关的高亮
canvas.addMarker(n.id, getActivityHighlightCss(activity))
n.outgoing?.forEach((nn: any) => {
// 获得连线是否有指向目标。如果有,则进行高亮
const targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
if (targetActivity) {
canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线
// 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。
canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))
}
})
} else if (n.$type === 'bpmn:StartEvent') {
// 开始节点
canvas.addMarker(n.id, 'highlight')
n.outgoing?.forEach(nn => {
// outgoing 例如说【bpmn:SequenceFlow】连线
// 获得连线是否有指向目标。如果有,则进行高亮
let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
if (targetActivity) {
canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线
canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点自己
}
})
} else if (n.$type === 'bpmn:EndEvent') {
// 结束节点
if (!processInstance.value || processInstance.value.status === 1) {
return
}
canvas.addMarker(n.id, getResultCss(processInstance.value.status))
} else if (n.$type === 'bpmn:ServiceTask') {
//服务任务
if (activity.startTime > 0 && activity.endTime === 0) {
//进入执行,标识进行色
canvas.addMarker(n.id, getResultCss(1))
}
if (activity.endTime > 0) {
// 执行完成,节点标识完成色, 所有outgoing标识完成色。
canvas.addMarker(n.id, getResultCss(2))
const outgoing = getActivityOutgoing(activity)
outgoing?.forEach((out: any) => {
canvas.addMarker(out.id, getResultCss(2))
})
}
} else if (n.$type === 'bpmn:SequenceFlow') {
let targetActivity = activityList.find((m: any) => m.key === n.targetRef.id)
if (targetActivity) {
canvas.addMarker(n.id, getActivityHighlightCss(targetActivity))
}
}
})
if (!(removeTaskDefinitionKeyList == null)) {
taskList.value = taskList.value.filter(item => !removeTaskDefinitionKeyList.includes(item.taskDefinitionKey))
}
}
const getActivityHighlightCss = (activity: any) => {
return activity.endTime ? 'highlight' : 'highlight-todo'
}
const getResultCss = (status: any) => {
if (status === 1) {
// 审批中
return 'highlight-todo'
} else if (status === 2) {
// 已通过
return 'highlight'
} else if (status === 3) {
// 不通过
return 'highlight-reject'
} else if (status === 4) {
// 已取消
return 'highlight-cancel'
} else if (status === 5) {
// 退回
return 'highlight-return'
} else if (status === 6) {
// 委派
return 'highlight-todo'
} else if (status === 7) {
// 审批通过中
return 'highlight-todo'
} else if (status === 0) {
// 待审批
return 'highlight-todo'
}
return ''
}
const getActivityOutgoing = (activity: any) => {
// 如果有 outgoing则直接使用它
if (activity.outgoing && activity.outgoing.length > 0) {
return activity.outgoing
}
// 如果没有则遍历获得起点为它的【bpmn:SequenceFlow】节点们。原因是bpmn-js 的 UserTask 拿不到 outgoing
const flowElements = bpmnModeler.getDefinitions().rootElements[0].flowElements
const outgoing: any[] = []
flowElements.forEach((item: any) => {
if (item.$type !== 'bpmn:SequenceFlow') {
return
}
if (item.sourceRef.id === activity.key) {
outgoing.push(item)
}
})
return outgoing
}
onMounted(() => {
xml.value = props.value
activityLists.value = props.activityData
// 初始化
initBpmnModeler()
createNewDiagram(xml.value)
})
onBeforeUnmount(() => {
// this.$once('hook:beforeDestroy', () => {
// })
if (bpmnModeler) bpmnModeler.destroy()
emit('destroy', bpmnModeler)
bpmnModeler = null
})
watch(
() => props.value,
newValue => {
xml.value = newValue
createNewDiagram(xml.value)
}
)
watch(
() => props.activityData,
newActivityData => {
activityLists.value = newActivityData
createNewDiagram(xml.value)
}
)
</script>
<style lang="scss">
/** 处理中 */
.highlight-todo.djs-connection > .djs-visual > path {
stroke: #1890ff !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: #1890ff !important;
stroke: #1890ff !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight-todo.djs-connection > .djs-visual > path) {
stroke: #1890ff !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
marker-end: url('#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr');
}
:deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
fill: #1890ff !important;
stroke: #1890ff !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
/** 通过 */
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
.highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: green !important; /* color elements as green */
}
:deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
fill: green !important;
}
:deep(.highlight.djs-shape .djs-visual > path) {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
:deep(.highlight.djs-connection > .djs-visual > path) {
stroke: green !important;
}
.djs-element.highlight > .djs-visual > path {
stroke: green !important;
}
/** 不通过 */
.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
fill: red !important;
stroke: red !important;
fill-opacity: 0.2 !important;
}
.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
fill: red !important;
}
.highlight-reject.djs-shape .djs-visual > path {
fill: red !important;
fill-opacity: 0.2 !important;
stroke: red !important;
}
.highlight-reject.djs-connection > .djs-visual > path {
stroke: red !important;
marker-end: url(#sequenceflow-end-white-success) !important;
}
.highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: red !important; /* color elements as green */
}
:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(1)) {
fill: red !important;
stroke: red !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(2)) {
fill: red !important;
}
:deep(.highlight-reject.djs-shape .djs-visual > path) {
fill: red !important;
fill-opacity: 0.2 !important;
stroke: red !important;
}
:deep(.highlight-reject.djs-connection > .djs-visual > path) {
stroke: red !important;
}
/** 已取消 */
.highlight-cancel.djs-shape .djs-visual > :nth-child(1) {
fill: grey !important;
stroke: grey !important;
fill-opacity: 0.2 !important;
}
.highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
fill: grey !important;
}
.highlight-cancel.djs-shape .djs-visual > path {
fill: grey !important;
fill-opacity: 0.2 !important;
stroke: grey !important;
}
.highlight-cancel.djs-connection > .djs-visual > path {
stroke: grey !important;
}
.highlight-cancel:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: grey !important; /* color elements as green */
}
:deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(1)) {
fill: grey !important;
stroke: grey !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(2)) {
fill: grey !important;
}
:deep(.highlight-cancel.djs-shape .djs-visual > path) {
fill: grey !important;
fill-opacity: 0.2 !important;
stroke: grey !important;
}
:deep(.highlight-cancel.djs-connection > .djs-visual > path) {
stroke: grey !important;
}
/** 回退 */
.highlight-return.djs-shape .djs-visual > :nth-child(1) {
fill: #e6a23c !important;
stroke: #e6a23c !important;
fill-opacity: 0.2 !important;
}
.highlight-return.djs-shape .djs-visual > :nth-child(2) {
fill: #e6a23c !important;
}
.highlight-return.djs-shape .djs-visual > path {
fill: #e6a23c !important;
fill-opacity: 0.2 !important;
stroke: #e6a23c !important;
}
.highlight-return.djs-connection > .djs-visual > path {
stroke: #e6a23c !important;
}
.highlight-return:not(.djs-connection) .djs-visual > :nth-child(1) {
fill: #e6a23c !important; /* color elements as green */
}
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(1)) {
fill: #e6a23c !important;
stroke: #e6a23c !important;
fill-opacity: 0.2 !important;
}
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
fill: #e6a23c !important;
}
:deep(.highlight-return.djs-shape .djs-visual > path) {
fill: #e6a23c !important;
fill-opacity: 0.2 !important;
stroke: #e6a23c !important;
}
:deep(.highlight-return.djs-connection > .djs-visual > path) {
stroke: #e6a23c !important;
}
.element-overlays {
width: 200px;
padding: 8px;
color: #fafafa;
background: rgb(0 0 0 / 60%);
border-radius: 4px;
box-sizing: border-box;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<el-dialog draggable v-model="dialogVisible" title="详情" width="1500">
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="表单信息" name="1">User</el-tab-pane>
<el-tab-pane label="流转记录" name="2">
<Recording ref="RecordingRef" />
</el-tab-pane>
<el-tab-pane label="流程图" name="3">
<ProcessViewer
v-if="bpmnXMLShow"
:key="activeName"
ref="ProcessViewerRef"
v-model="bpmnXML"
:value="bpmnXML"
/>
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { flowXmlAndNode } from '@/api/process-boot/bpm'
import Recording from './recording.vue'
import ProcessViewer from './ProcessViewer.vue'
const dialogVisible = ref(false)
const activeName = ref(1)
const RecordingRef = ref()
const ProcessViewerRef = ref()
const bpmnXMLShow = ref(false)
const bpmnXML: any = ref(null)
const open = async (row: any) => {
dialogVisible.value = true
await flowXmlAndNode({
deployId: row.deployId,
procInsId: row.procInsId
}).then(res => {
bpmnXML.value = res.data.xmlData
})
bpmnXMLShow.value = true
await nextTick(() => {
RecordingRef.value.open(row)
// ProcessViewerRef.value.open(bpmnXML.value)
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,101 @@
<template>
<el-col :span="12" :offset="6" class="mt10">
<el-timeline class="box">
<el-timeline-item
v-for="(item, index) in flowRecordList"
:key="index"
:icon="setIcon(item.finishTime)"
:color="setColor(item.finishTime)"
>
<p style="font-weight: 700">{{ item.taskName }}</p>
<el-card :body-style="{ padding: '10px' }">
<el-descriptions :column="1" border>
<el-descriptions-item v-if="item.assigneeName">
<template #label>
<Icon class="elIcon" name="el-icon-User" />
办理人
</template>
{{ item.assigneeName }}
<el-tag type="info">{{ item.deptName }}</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="item.candidate">
<template #label>
<Icon class="elIcon" name="el-icon-User" />
候选办理
</template>
{{ item.candidate }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<Icon class="elIcon" name="el-icon-Calendar" />
接收时间
</template>
{{ item.createTime }}
</el-descriptions-item>
<el-descriptions-item v-if="item.finishTime">
<template #label>
<Icon class="elIcon" name="el-icon-Calendar" />
处理时间
</template>
{{ item.finishTime }}
</el-descriptions-item>
<el-descriptions-item v-if="item.duration">
<template #label>
<Icon class="elIcon" name="el-icon-Timer" />
耗时
</template>
{{ item.duration }}
</el-descriptions-item>
<el-descriptions-item v-if="item.comment">
<template #label>
<Icon class="elIcon" name="el-icon-Tickets" />
处理意见
</template>
{{ item.comment.comment }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</el-timeline-item>
</el-timeline>
</el-col>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { flowRecord } from '@/api/process-boot/bpm'
const flowRecordList: any = ref([])
// 设置icon
const setIcon = (val: any) => {
if (val) {
return 'el-icon-Check'
} else {
return 'el-icon-Time'
}
}
// 设置颜色
const setColor = (val: any) => {
if (val) {
return '#2bc418'
} else {
return '#b3bdbb'
}
}
const open = (row: any) => {
flowRecord({ procInsId: row.procInsId, deployId: row.deployId }).then((res: any) => {
flowRecordList.value = res.data.flowList
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
:deep(.box) {
.elIcon {
font-size: 14px !important;
position: relative;
top: 3px;
}
.el-descriptions__label {
width: 200px;
}
}
</style>

View File

@@ -1,18 +1,17 @@
<!--业务用户管理界面--> <!--业务用户管理界面-->
<template> <template>
<div class="default-main"> <div class="default-main">
<TableHeader > <TableHeader>
<template v-slot:select> <template v-slot:select>
<el-form-item label="筛选数据"> <el-form-item label="筛选数据">
<el-input v-model="tableStore.table.params.name" clearable placeholder="筛选数据" /> <el-input v-model="tableStore.table.params.name" clearable placeholder="筛选数据" />
</el-form-item> </el-form-item>
</template> </template>
<template v-slot:operation>
<el-button type="primary" @click="exportEvent" class="ml10" icon="el-icon-Download">导出</el-button>
</template>
</TableHeader> </TableHeader>
<!--表格--> <!--表格-->
<Table ref="tableRef"></Table> <Table ref="tableRef"></Table>
<!-- 详情 -->
<Detail ref="detailRef" />
</div> </div>
</template> </template>
@@ -24,18 +23,17 @@ import TableHeader from '@/components/table/header/index.vue'
import { onMounted, provide, ref } from 'vue' import { onMounted, provide, ref } from 'vue'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { pageTable } from '@/api/harmonic-boot/luckyexcel' import { pageTable } from '@/api/harmonic-boot/luckyexcel'
import Detail from './components/detail.vue'
defineOptions({ defineOptions({
name: 'harmonic-boot/reate/word' name: '/flowTask/mytask'
}) })
const dictData = useDictData() const dictData = useDictData()
//区域联级选择
const industry = dictData.getBasicData('Interference_Source')
//用户信息弹出框
const tableRef = ref() const tableRef = ref()
const detailRef = ref()
const tableStore = new TableStore({ const tableStore = new TableStore({
url: '/process-boot/flowable/task/todoList', url: '/process-boot/flowable/task/myProcess',
method: 'GET', method: 'GET',
column: [ column: [
{ {
@@ -45,59 +43,85 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1 return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
} }
}, },
{ title: '变电站', field: 'subName', width: 200 }, { title: '流程名称', field: 'procDefName', width: 200 },
{ title: '监测点名称', field: 'lineName', width: 200 }, { title: '流程类别', field: 'category' },
{ {
title: '行业类型', title: '流程版本',
field: 'businessType', field: 'procDefVersion'
formatter: (row: any) => {
return industry.find((item: any) => item.id == row.cellValue)?.name || '/'
}
}, },
{ {
title: '分类等级', title: '提交时间',
field: 'calssificationGrade', field: 'createTime'
formatter: (row: any) => { },
return row.cellValue || '/' {
title: '流程状态',
field: 'finishTime',
render: 'tag',
custom: {
0: 'warning',
1: 'success'
},
replaceValue: {
0: '进行中',
1: '已完成'
} }
}, },
{ title: '电压等级', field: 'voltageScale' }, { title: '耗时', field: 'duration' },
{ {
title: '上级变电站', title: '当前节点',
field: 'superiorsSubstation', field: 'taskName'
formatter: (row: any) => { },
return row.cellValue || '/' {
} title: '办理人',
field: 'assigneeName'
},
{
title: '操作',
width: '180',
render: 'buttons',
buttons: [
{
name: 'edit',
title: '详情',
type: 'primary',
icon: 'el-icon-Plus',
render: 'basicButton',
click: row => {
detailRef.value.open(row)
}
},
{
name: 'del',
text: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'confirmButton',
popconfirm: {
confirmButtonText: '确认',
cancelButtonText: '取消',
confirmButtonType: 'danger',
title: '确定删除?'
},
click: row => {}
}
]
} }
], ],
beforeSearchFun: () => { beforeSearchFun: () => {
tableStore.table.params.beginTime = tableStore.table.params.startTime tableStore.table.params.beginTime = tableStore.table.params.startTime
tableStore.table.params.deptId = tableStore.table.params.deptIndex tableStore.table.params.deptId = tableStore.table.params.deptIndex
},
loadCallback: () => {
tableStore.table.data.forEach((item: any) => {
item.finishTime = item.finishTime == null ? '0' : '1'
})
} }
}) })
onMounted(() => { onMounted(() => {
// 加载数据
tableStore.index() tableStore.index()
}) })
tableStore.table.params.name = '' tableStore.table.params.name = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
// 导出
const exportEvent = () => {
let form = JSON.parse(JSON.stringify(tableStore.table.params))
form.pageNum = 1
form.pageSize = tableStore.table.total
pageTable(form).then(res => {
tableRef.value.getRef().exportData({
filename: '合格率报告', // 文件名字
sheetName: 'Sheet1',
type: 'xlsx', //导出文件类型 xlsx 和 csv
useStyle: true,
data: res.data.records, // 数据源 // 过滤那个字段导出
columnFilterMethod: function (column: any) {
return !(column.$columnIndex === 0)
}
})
})
}
</script> </script>

View File

@@ -8,7 +8,6 @@
clearable clearable
:options="tableStore.table.data" :options="tableStore.table.data"
:props="cascaderProps" :props="cascaderProps"
clearable
style="width: 100%" style="width: 100%"
/> />
</el-form-item> </el-form-item>

View File

@@ -11,6 +11,7 @@ export default defineConfig({
plugins: [vue(), svgBuilder('./src/assets/icons/'), vueJsx()], plugins: [vue(), svgBuilder('./src/assets/icons/'), vueJsx()],
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
open: true,
proxy: { proxy: {
'/api': { '/api': {
// target: 'http://192.168.1.31:10215', //数据中心 // target: 'http://192.168.1.31:10215', //数据中心