Merge branch 'dev' of gitee.com:yudaocode/yudao-ui-admin-vue3 into hotfix-role
Signed-off-by: AhJindeg <ahjindeg@gmail.com>
This commit is contained in:
@@ -188,7 +188,7 @@ const loginData = reactive({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
rememberMe: false
|
||||
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
||||
}
|
||||
})
|
||||
|
||||
@@ -218,14 +218,14 @@ const getTenantId = async () => {
|
||||
}
|
||||
}
|
||||
// 记住我
|
||||
const getCookie = () => {
|
||||
const getLoginFormCache = () => {
|
||||
const loginForm = authUtil.getLoginForm()
|
||||
if (loginForm) {
|
||||
loginData.loginForm = {
|
||||
...loginData.loginForm,
|
||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||
rememberMe: loginForm.rememberMe ? true : false,
|
||||
rememberMe: loginForm.rememberMe,
|
||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ watch(
|
||||
}
|
||||
)
|
||||
onMounted(() => {
|
||||
getCookie()
|
||||
getLoginFormCache()
|
||||
getTenantByWebsite()
|
||||
})
|
||||
</script>
|
||||
|
||||
124
src/views/bpm/category/CategoryForm.vue
Normal file
124
src/views/bpm/category/CategoryForm.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="分类名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入分类名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类标志" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入分类标志" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类排序" prop="sort">
|
||||
<el-input-number
|
||||
v-model="formData.sort"
|
||||
placeholder="请输入分类排序"
|
||||
class="!w-1/1"
|
||||
:precision="0"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
|
||||
/** BPM 流程分类 表单 */
|
||||
defineOptions({ name: 'CategoryForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
sort: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '分类名不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '分类标志不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '分类状态不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CategoryApi.getCategory(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as CategoryVO
|
||||
if (formType.value === 'create') {
|
||||
await CategoryApi.createCategory(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CategoryApi.updateCategory(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
sort: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,6 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
@@ -8,19 +10,28 @@
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="文件名称" prop="name">
|
||||
<el-form-item label="分类名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入文件名称"
|
||||
placeholder="请输入分类名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-form-item label="分类标志" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
placeholder="请输入分类标志"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择状态"
|
||||
placeholder="请选择分类状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
@@ -32,15 +43,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="queryParams.remark"
|
||||
placeholder="请输入备注"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
@@ -59,19 +61,10 @@
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['report:ureport-data:create']"
|
||||
v-hasPermi="['bpm:category:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['report:ureport-data:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@@ -79,15 +72,16 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="ID" align="center" prop="id" />
|
||||
<el-table-column label="文件名称" align="center" prop="name" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<el-table-column label="分类编号" align="center" prop="id" />
|
||||
<el-table-column label="分类名" align="center" prop="name" />
|
||||
<el-table-column label="分类标志" align="center" prop="code" />
|
||||
<el-table-column label="分类描述" align="center" prop="description" />
|
||||
<el-table-column label="分类状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件内容" align="center" prop="content" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="分类排序" align="center" prop="sort" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
@@ -101,7 +95,7 @@
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['report:ureport-data:update']"
|
||||
v-hasPermi="['bpm:category:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
@@ -109,7 +103,7 @@
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['report:ureport-data:delete']"
|
||||
v-hasPermi="['bpm:category:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
@@ -126,31 +120,32 @@
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<UReportDataForm ref="formRef" @success="getList" />
|
||||
<CategoryForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as UReportDataApi from '@/api/report/ureport'
|
||||
import UReportDataForm from './UReportDataForm.vue'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import CategoryForm from './CategoryForm.vue'
|
||||
|
||||
defineOptions({ name: 'UReportData' })
|
||||
/** BPM 流程分类 列表 */
|
||||
defineOptions({ name: 'BpmCategory' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const list = ref<CategoryVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
status: null,
|
||||
remark: null,
|
||||
createTime: [],
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
@@ -159,7 +154,7 @@ const exportLoading = ref(false) // 导出的加载中
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await UReportDataApi.getUReportDataPage(queryParams)
|
||||
const data = await CategoryApi.getCategoryPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@@ -191,28 +186,13 @@ const handleDelete = async (id: number) => {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UReportDataApi.deleteUReportData(id)
|
||||
await CategoryApi.deleteCategory(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await UReportDataApi.exportUReportData(queryParams)
|
||||
download.excel(data, 'Ureport2报表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
@@ -11,11 +11,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="定义分类" align="center" prop="category" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="定义分类" align="center" prop="categoryName" width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -57,18 +53,6 @@
|
||||
width="300"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleAssignRule(scope.row)"
|
||||
v-hasPermi="['bpm:task-assign-rule:query']"
|
||||
>
|
||||
分配规则
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
@@ -88,8 +72,8 @@
|
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
|
||||
<MyProcessViewer
|
||||
key="designer"
|
||||
v-model="bpmnXML"
|
||||
:value="bpmnXML as any"
|
||||
v-model="bpmnXml"
|
||||
:value="bpmnXml as any"
|
||||
v-bind="bpmnControlForm"
|
||||
:prefix="bpmnControlForm.prefix"
|
||||
/>
|
||||
@@ -97,7 +81,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
@@ -129,16 +112,6 @@ const getList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击任务分配按钮 */
|
||||
const handleAssignRule = (row) => {
|
||||
push({
|
||||
name: 'BpmTaskAssignRuleList',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 流程表单的详情按钮操作 */
|
||||
const formDetailVisible = ref(false)
|
||||
const formDetailPreview = ref({
|
||||
@@ -160,12 +133,12 @@ const handleFormDetail = async (row) => {
|
||||
|
||||
/** 流程图的详情按钮操作 */
|
||||
const bpmnDetailVisible = ref(false)
|
||||
const bpmnXML = ref(null)
|
||||
const bpmnXml = ref(null)
|
||||
const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const handleBpmnDetail = async (row) => {
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
|
||||
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
|
||||
bpmnDetailVisible.value = true
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import * as FormApi from '@/api/bpm/form'
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { useFormCreateDesigner } from '@/components/FormCreate'
|
||||
|
||||
defineOptions({ name: 'BpmFormEditor' })
|
||||
|
||||
@@ -55,6 +56,7 @@ const { query } = useRoute() // 路由信息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const designer = ref() // 表单设计器
|
||||
useFormCreateDesigner(designer) // 表单设计器增强
|
||||
const dialogVisible = ref(false) // 弹窗是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
|
||||
const formData = ref({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="审批接入(流程表单)" url="https://doc.iocoder.cn/bpm/use-bpm-form/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="formData.description" placeholder="请输入描述" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成员" prop="memberUserIds">
|
||||
<el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员">
|
||||
<el-form-item label="成员" prop="userIds">
|
||||
<el-select v-model="formData.userIds" multiple placeholder="请选择成员">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
@@ -60,13 +60,13 @@ const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
memberUserIds: undefined,
|
||||
userIds: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
|
||||
memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
|
||||
userIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
@@ -124,7 +124,7 @@ const resetForm = () => {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
memberUserIds: undefined,
|
||||
userIds: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<el-table-column label="描述" align="center" prop="description" />
|
||||
<el-table-column label="成员" align="center">
|
||||
<template #default="scope">
|
||||
<span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px">
|
||||
<span v-for="userId in scope.row.userIds" :key="userId" class="pr-5px">
|
||||
{{ userList.find((user) => user.id === userId)?.nickname }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -43,13 +43,16 @@
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.id" label="流程图标" prop="icon">
|
||||
<UploadImg v-model="formData.icon" :limit="1" height="128px" width="128px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程描述" prop="description">
|
||||
<el-input v-model="formData.description" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
@@ -126,6 +129,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'ModelForm' })
|
||||
|
||||
@@ -140,20 +144,23 @@ const formData = ref({
|
||||
formType: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
icon: undefined,
|
||||
description: '',
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
formCustomViewPath: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
|
||||
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
|
||||
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
|
||||
icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }],
|
||||
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
|
||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const formList = ref([]) // 流程表单的下拉框的数据
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
@@ -171,7 +178,9 @@ const open = async (type: string, id?: number) => {
|
||||
}
|
||||
}
|
||||
// 获得流程表单的下拉框的数据
|
||||
formList.value = await FormApi.getSimpleFormList()
|
||||
formList.value = await FormApi.getFormSimpleList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
@@ -190,11 +199,10 @@ const submitForm = async () => {
|
||||
await ModelApi.createModel(data)
|
||||
// 提示,引导用户做后续的操作
|
||||
await ElMessageBox.alert(
|
||||
'<strong>新建模型成功!</strong>后续需要执行如下 4 个步骤:' +
|
||||
'<strong>新建模型成功!</strong>后续需要执行如下 3 个步骤:' +
|
||||
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
|
||||
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
|
||||
'<div>3. 点击【分配规则】按钮,设置每个用户任务的审批人</div>' +
|
||||
'<div>4. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
||||
'<div>3. 点击【发布流程】按钮,完成流程的最终发布</div>' +
|
||||
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
|
||||
'重要提示',
|
||||
{
|
||||
@@ -220,6 +228,7 @@ const resetForm = () => {
|
||||
formType: 10,
|
||||
name: '',
|
||||
category: undefined,
|
||||
icon: '',
|
||||
description: '',
|
||||
formId: '',
|
||||
formCustomCreatePath: '',
|
||||
|
||||
@@ -109,6 +109,7 @@ const submitFormSuccess = async (response: any) => {
|
||||
}
|
||||
// 提示成功
|
||||
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
}
|
||||
|
||||
@@ -89,11 +89,21 @@ onMounted(async () => {
|
||||
}
|
||||
// 查询模型
|
||||
const data = await ModelApi.getModel(modelId)
|
||||
xmlString.value = data.bpmnXml
|
||||
if (!data.bpmnXml) {
|
||||
// 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
|
||||
data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
|
||||
<process id="${data.key}" name="${data.name}" isExecutable="true" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram">
|
||||
<bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>`
|
||||
}
|
||||
model.value = {
|
||||
...data,
|
||||
bpmnXml: undefined // 清空 bpmnXml 属性
|
||||
}
|
||||
xmlString.value = data.bpmnXml
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
|
||||
<doc-alert
|
||||
title="流程设计器(钉钉、飞书)"
|
||||
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
|
||||
/>
|
||||
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
|
||||
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
@@ -36,10 +42,10 @@
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -72,11 +78,12 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="category" width="100">
|
||||
<el-table-column label="流程图标" align="center" prop="icon" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
|
||||
<el-image :src="scope.row.icon" class="w-32px h-32px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程分类" align="center" prop="categoryName" width="100" />
|
||||
<el-table-column label="表单信息" align="center" prop="formType" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -164,10 +171,10 @@
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleAssignRule(scope.row)"
|
||||
v-hasPermi="['bpm:task-assign-rule:query']"
|
||||
@click="handleSimpleDesign(scope.row.id)"
|
||||
v-hasPermi="['bpm:model:update']"
|
||||
>
|
||||
分配规则
|
||||
仿钉钉设计流程
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
@@ -229,7 +236,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
@@ -237,6 +243,7 @@ import * as FormApi from '@/api/bpm/form'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmModel' })
|
||||
|
||||
@@ -255,6 +262,7 @@ const queryParams = reactive({
|
||||
category: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@@ -334,6 +342,15 @@ const handleDesign = (row) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleSimpleDesign = (row) => {
|
||||
push({
|
||||
name: 'SimpleWorkflowDesignEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 发布流程 */
|
||||
const handleDeploy = async (row) => {
|
||||
try {
|
||||
@@ -347,16 +364,6 @@ const handleDeploy = async (row) => {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 点击任务分配按钮 */
|
||||
const handleAssignRule = (row) => {
|
||||
push({
|
||||
name: 'BpmTaskAssignRuleList',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
@@ -400,7 +407,9 @@ const handleBpmnDetail = async (row) => {
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 查询流程分类列表
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -37,6 +37,36 @@
|
||||
<el-form-item label="原因" prop="reason">
|
||||
<el-input v-model="formData.reason" placeholder="请输请假原因" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-col v-if="startUserSelectTasks.length > 0">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-form-item>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
</el-form-item>
|
||||
@@ -46,10 +76,15 @@
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmOALeaveCreate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: undefined,
|
||||
@@ -64,18 +99,34 @@ const formRules = reactive({
|
||||
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
|
||||
// 指定审批人
|
||||
const processDefineKey = 'oa_leave' // 流程定义 Key
|
||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 校验指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as LeaveApi.LeaveVO
|
||||
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
data.startUserSelectAssignees = startUserSelectAssignees.value
|
||||
}
|
||||
await LeaveApi.createLeave(data)
|
||||
message.success('发起成功')
|
||||
// 关闭当前 Tab
|
||||
@@ -85,4 +136,29 @@ const submitForm = async () => {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
||||
undefined,
|
||||
processDefineKey
|
||||
)
|
||||
if (!processDefinitionDetail) {
|
||||
message.error('OA 请假的流程模型未配置,请检查!')
|
||||
return
|
||||
}
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="审批接入(业务表单)" url="https://doc.iocoder.cn/bpm/use-business-form/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
@@ -36,10 +36,15 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果" prop="result">
|
||||
<el-select v-model="queryParams.result" class="!w-240px" clearable placeholder="请选择结果">
|
||||
<el-form-item label="审批结果" prop="result">
|
||||
<el-select
|
||||
v-model="queryParams.result"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择审批结果"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
@@ -78,7 +83,7 @@
|
||||
<el-table-column align="center" label="申请编号" prop="id" />
|
||||
<el-table-column align="center" label="状态" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -166,7 +171,7 @@ const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: undefined,
|
||||
result: undefined,
|
||||
status: undefined,
|
||||
reason: undefined,
|
||||
createTime: []
|
||||
})
|
||||
@@ -221,7 +226,7 @@ const cancelLeave = async (row) => {
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
|
||||
@@ -7,24 +7,22 @@
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="文件名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入文件名称" />
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态">
|
||||
<el-option
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件内容" prop="content">
|
||||
<Editor v-model="formData.content" height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
<el-form-item label="表达式" prop="expression">
|
||||
<el-input type="textarea" v-model="formData.expression" placeholder="请输入表达式" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -35,7 +33,11 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import * as UReportDataApi from '@/api/report/ureport'
|
||||
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessExpressionForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
@@ -48,12 +50,12 @@ const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
content: undefined,
|
||||
remark: undefined,
|
||||
expression: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '文件名称不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
expression: [{ required: true, message: '表达式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
@@ -67,7 +69,7 @@ const open = async (type: string, id?: number) => {
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await UReportDataApi.getUReportData(id)
|
||||
formData.value = await ProcessExpressionApi.getProcessExpression(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
@@ -83,12 +85,12 @@ const submitForm = async () => {
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as UReportDataApi.UReportDataVO
|
||||
const data = formData.value as unknown as ProcessExpressionVO
|
||||
if (formType.value === 'create') {
|
||||
await UReportDataApi.createUReportData(data)
|
||||
await ProcessExpressionApi.createProcessExpression(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await UReportDataApi.updateUReportData(data)
|
||||
await ProcessExpressionApi.updateProcessExpression(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
@@ -104,10 +106,9 @@ const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
content: undefined,
|
||||
remark: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
expression: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
182
src/views/bpm/processExpression/index.vue
Normal file
182
src/views/bpm/processExpression/index.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<doc-alert title="流程表达式" url="https://doc.iocoder.cn/bpm/expression/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:process-expression:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="表达式" align="center" prop="expression" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:process-expression:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:process-expression:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ProcessExpressionForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
|
||||
import ProcessExpressionForm from './ProcessExpressionForm.vue'
|
||||
|
||||
/** BPM 流程表达式列表 */
|
||||
defineOptions({ name: 'BpmProcessExpression' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ProcessExpressionVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessExpressionApi.getProcessExpressionPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ProcessExpressionApi.deleteProcessExpression(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -1,35 +1,47 @@
|
||||
<template>
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<ContentWrap v-if="!selectProcessInstance">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" />
|
||||
<el-table-column label="流程分类" align="center" prop="category">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程版本" align="center" prop="version">
|
||||
<template #default="scope">
|
||||
<el-tag>v{{ scope.row.version }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程描述" align="center" prop="description" />
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleSelect(scope.row)">
|
||||
<Icon icon="ep:plus" /> 选择
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
|
||||
<el-tabs tab-position="left" v-model="categoryActive">
|
||||
<el-tab-pane
|
||||
:label="category.name"
|
||||
:name="category.code"
|
||||
:key="category.code"
|
||||
v-for="category in categoryList"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
:lg="6"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
v-for="definition in categoryProcessDefinitionList"
|
||||
:key="definition.id"
|
||||
>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="mb-20px cursor-pointer"
|
||||
@click="handleSelect(definition)"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<el-image :src="definition.icon" class="w-32px h-32px" />
|
||||
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 -->
|
||||
<ContentWrap v-else>
|
||||
<el-card class="box-card">
|
||||
<div class="clearfix">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessInstance.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessInstance = undefined">
|
||||
<span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
|
||||
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
|
||||
<Icon icon="ep:delete" /> 选择其它流程
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -37,9 +49,43 @@
|
||||
<form-create
|
||||
:rule="detailForm.rule"
|
||||
v-model:api="fApi"
|
||||
v-model="detailForm.value"
|
||||
:option="detailForm.option"
|
||||
@submit="submitForm"
|
||||
/>
|
||||
>
|
||||
<template #type-startUserSelect>
|
||||
<el-col :span="24">
|
||||
<el-card class="mb-10px">
|
||||
<template #header>指定审批人</template>
|
||||
<el-form
|
||||
:model="startUserSelectAssignees"
|
||||
:rules="startUserSelectAssigneesFormRules"
|
||||
ref="startUserSelectAssigneesFormRef"
|
||||
>
|
||||
<el-form-item
|
||||
v-for="userTask in startUserSelectTasks"
|
||||
:key="userTask.id"
|
||||
:label="`任务【${userTask.name}】`"
|
||||
:prop="userTask.id"
|
||||
>
|
||||
<el-select
|
||||
v-model="startUserSelectAssignees[userTask.id]"
|
||||
multiple
|
||||
placeholder="请选择审批人"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</form-create>
|
||||
</el-col>
|
||||
</el-card>
|
||||
<!-- 流程图预览 -->
|
||||
@@ -47,59 +93,127 @@
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceCreate' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const route = useRoute() // 路由
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const message = useMessage() // 消息
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
suspensionState: 1
|
||||
})
|
||||
const processInstanceId = route.query.processInstanceId
|
||||
const loading = ref(true) // 加载中
|
||||
const categoryList = ref([]) // 分类的列表
|
||||
const categoryActive = ref('') // 选中的分类
|
||||
const processDefinitionList = ref([]) // 流程定义的列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = await DefinitionApi.getProcessDefinitionList(queryParams)
|
||||
// 流程分类
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
if (categoryList.value.length > 0) {
|
||||
categoryActive.value = categoryList.value[0].code
|
||||
}
|
||||
// 流程定义
|
||||
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
|
||||
suspensionState: 1
|
||||
})
|
||||
|
||||
// 如果 processInstanceId 非空,说明是重新发起
|
||||
if (processInstanceId?.length > 0) {
|
||||
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
|
||||
if (!processInstance) {
|
||||
message.error('重新发起流程失败,原因:流程实例不存在')
|
||||
return
|
||||
}
|
||||
const processDefinition = processDefinitionList.value.find(
|
||||
(item) => item.key == processInstance.processDefinition?.key
|
||||
)
|
||||
if (!processDefinition) {
|
||||
message.error('重新发起流程失败,原因:流程定义不存在')
|
||||
return
|
||||
}
|
||||
await handleSelect(processDefinition, processInstance.formVariables)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 选中分类对应的流程定义列表 */
|
||||
const categoryProcessDefinitionList = computed(() => {
|
||||
return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
|
||||
})
|
||||
|
||||
// ========== 表单相关 ==========
|
||||
const bpmnXML = ref(null) // BPMN 数据
|
||||
const fApi = ref<ApiAttrs>()
|
||||
const detailForm = ref({
|
||||
// 流程表单详情
|
||||
rule: [],
|
||||
option: {}
|
||||
})
|
||||
const selectProcessInstance = ref() // 选择的流程实例
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程表单详情
|
||||
const selectProcessDefinition = ref() // 选择的流程定义
|
||||
|
||||
// 指定审批人
|
||||
const bpmnXML = ref(null) // BPMN 数据
|
||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 处理选择流程的按钮操作 **/
|
||||
const handleSelect = async (row) => {
|
||||
const handleSelect = async (row, formVariables) => {
|
||||
// 设置选择的流程
|
||||
selectProcessInstance.value = row
|
||||
selectProcessDefinition.value = row
|
||||
|
||||
// 重置指定审批人
|
||||
startUserSelectTasks.value = []
|
||||
startUserSelectAssignees.value = {}
|
||||
startUserSelectAssigneesFormRules.value = {}
|
||||
|
||||
// 情况一:流程表单
|
||||
if (row.formType == 10) {
|
||||
// 设置表单
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields)
|
||||
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
|
||||
// 加载流程图
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
|
||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
|
||||
if (processDefinitionDetail) {
|
||||
bpmnXML.value = processDefinitionDetail.bpmnXml
|
||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||
|
||||
// 设置指定审批人
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
detailForm.value.rule.push({
|
||||
type: 'startUserSelect',
|
||||
props: {
|
||||
title: '指定审批人'
|
||||
}
|
||||
})
|
||||
// 设置校验规则
|
||||
for (const userTask of startUserSelectTasks.value) {
|
||||
startUserSelectAssignees.value[userTask.id] = []
|
||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
// 加载用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
}
|
||||
}
|
||||
// 情况二:业务表单
|
||||
} else if (row.formCustomCreatePath) {
|
||||
await router.push({
|
||||
await push({
|
||||
path: row.formCustomCreatePath
|
||||
})
|
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab;
|
||||
@@ -108,19 +222,29 @@ const handleSelect = async (row) => {
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async (formData) => {
|
||||
if (!fApi.value || !selectProcessInstance.value) {
|
||||
if (!fApi.value || !selectProcessDefinition.value) {
|
||||
return
|
||||
}
|
||||
// 如果有指定审批人,需要校验
|
||||
if (startUserSelectTasks.value?.length > 0) {
|
||||
await startUserSelectAssigneesFormRef.value.validate()
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
fApi.value.btn.loading(true)
|
||||
try {
|
||||
await ProcessInstanceApi.createProcessInstance({
|
||||
processDefinitionId: selectProcessInstance.value.id,
|
||||
variables: formData
|
||||
processDefinitionId: selectProcessDefinition.value.id,
|
||||
variables: formData,
|
||||
startUserSelectAssignees: startUserSelectAssignees.value
|
||||
})
|
||||
// 提示
|
||||
message.success('发起流程成功')
|
||||
router.go(-1)
|
||||
// 跳转回去
|
||||
delView(unref(currentRoute))
|
||||
await push({
|
||||
name: 'BpmProcessInstanceMy'
|
||||
})
|
||||
} finally {
|
||||
fApi.value.btn.loading(false)
|
||||
}
|
||||
|
||||
@@ -33,21 +33,18 @@ const bpmnControlForm = ref({
|
||||
prefix: 'flowable'
|
||||
})
|
||||
const activityList = ref([]) // 任务列表
|
||||
// const bpmnXML = computed(() => { // TODO 芋艿:不晓得为啊哈不能这么搞
|
||||
// if (!props.processInstance || !props.processInstance.processDefinition) {
|
||||
// return
|
||||
// }
|
||||
// return DefinitionApi.getProcessDefinitionBpmnXML(props.processInstance.processDefinition.id)
|
||||
// })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
if (props.id) {
|
||||
activityList.value = await ActivityApi.getActivityList({
|
||||
processInstanceId: props.id
|
||||
})
|
||||
/** 只有 loading 完成时,才去加载流程列表 */
|
||||
watch(
|
||||
() => props.loading,
|
||||
async (value) => {
|
||||
if (value && props.id) {
|
||||
activityList.value = await ActivityApi.getActivityList({
|
||||
processInstanceId: props.id
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.box-card {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="子任务" size="70%">
|
||||
<!-- 当前任务 -->
|
||||
<template #header>
|
||||
<h4>【{{ baseTask.name }} 】审批人:{{ baseTask.assigneeUser?.nickname }}</h4>
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="isSubSignButtonVisible(baseTask)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSubSign(baseTask)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 子任务列表 -->
|
||||
<el-table :data="baseTask.children" style="width: 100%" row-key="id" border>
|
||||
<el-table-column prop="assigneeUser.nickname" label="审批人" />
|
||||
<el-table-column prop="assigneeUser.deptName" label="所在部门" />
|
||||
<el-table-column label="审批状态" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" prop="operation">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isSubSignButtonVisible(scope.row)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSubSign(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 减签 -->
|
||||
<TaskSubSignDialogForm ref="taskSubSignDialogForm" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import TaskSubSignDialogForm from './TaskSubSignDialogForm.vue'
|
||||
|
||||
defineOptions({ name: 'ProcessInstanceChildrenTaskList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||
|
||||
const baseTask = ref<object>({})
|
||||
/** 打开弹窗 */
|
||||
const open = async (task: any) => {
|
||||
if (isEmpty(task.children)) {
|
||||
message.warning('该任务没有子任务')
|
||||
return
|
||||
}
|
||||
baseTask.value = task
|
||||
// 展开抽屉
|
||||
drawerVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 发起减签 */
|
||||
const taskSubSignDialogForm = ref()
|
||||
const handleSubSign = (item) => {
|
||||
taskSubSignDialogForm.value.open(item.id)
|
||||
// TODO @海洋:减签后,需要刷新下界面哈
|
||||
}
|
||||
|
||||
/** 是否显示减签按钮 */
|
||||
const isSubSignButtonVisible = (task: any) => {
|
||||
if (task && task.children && !isEmpty(task.children)) {
|
||||
// 有子任务,且子任务有任意一个是 待处理 和 待前置任务完成 则显示减签按钮
|
||||
const subTask = task.children.find((item) => item.result === 1 || item.result === 9)
|
||||
return !isEmpty(subTask)
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
@@ -3,25 +3,44 @@
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">审批记录</span>
|
||||
</template>
|
||||
<el-col :offset="4" :span="16">
|
||||
<el-col :offset="3" :span="17">
|
||||
<div class="block">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in tasks"
|
||||
:key="index"
|
||||
:icon="getTimelineItemIcon(item)"
|
||||
:type="getTimelineItemType(item)"
|
||||
v-if="processInstance.endTime"
|
||||
:type="getProcessInstanceTimelineItemType(processInstance)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
任务:{{ item.name }}
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="item.result" />
|
||||
结束流程:在 {{ formatDate(processInstance?.endTime) }} 结束
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
|
||||
:value="processInstance.status"
|
||||
/>
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in tasks"
|
||||
:key="index"
|
||||
:type="getTaskTimelineItemType(item)"
|
||||
>
|
||||
<p style="font-weight: 700">
|
||||
审批任务:{{ item.name }}
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
class="ml-10px"
|
||||
v-if="!isEmpty(item.children)"
|
||||
@click="openChildrenTask(item)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:memo" />
|
||||
子任务
|
||||
<Icon icon="ep:memo" /> 子任务
|
||||
</el-button>
|
||||
<el-button
|
||||
class="ml-10px"
|
||||
size="small"
|
||||
v-if="item.formId > 0"
|
||||
@click="handleFormDetail(item)"
|
||||
>
|
||||
<Icon icon="ep:document" /> 查看表单
|
||||
</el-button>
|
||||
</p>
|
||||
<el-card :body-style="{ padding: '10px' }">
|
||||
@@ -45,84 +64,112 @@
|
||||
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
|
||||
{{ formatPast2(item?.durationInMillis) }}
|
||||
</label>
|
||||
<p v-if="item.reason">
|
||||
<el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>
|
||||
</p>
|
||||
<p v-if="item.reason"> 审批建议:{{ item.reason }} </p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item type="success">
|
||||
<p style="font-weight: 700">
|
||||
发起流程:【{{ processInstance.startUser?.nickname }}】在
|
||||
{{ formatDate(processInstance?.startTime) }} 发起【 {{ processInstance.name }} 】流程
|
||||
</p>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- 子任务 -->
|
||||
<ProcessInstanceChildrenTaskList ref="processInstanceChildrenTaskList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 弹窗:子任务 -->
|
||||
<TaskSignList ref="taskSignListRef" @success="refresh" />
|
||||
<!-- 弹窗:表单 -->
|
||||
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
|
||||
<form-create
|
||||
ref="fApi"
|
||||
v-model="taskForm.value"
|
||||
:option="taskForm.option"
|
||||
:rule="taskForm.rule"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import ProcessInstanceChildrenTaskList from './ProcessInstanceChildrenTaskList.vue'
|
||||
import TaskSignList from './dialog/TaskSignList.vue'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||
|
||||
defineProps({
|
||||
loading: propTypes.bool, // 是否加载中
|
||||
processInstance: propTypes.object, // 流程实例
|
||||
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
|
||||
})
|
||||
|
||||
/** 获得任务对应的 icon */
|
||||
const getTimelineItemIcon = (item) => {
|
||||
if (item.result === 1) {
|
||||
return 'el-icon-time'
|
||||
/** 获得流程实例对应的颜色 */
|
||||
const getProcessInstanceTimelineItemType = (item: any) => {
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.result === 2) {
|
||||
return 'el-icon-check'
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.result === 3) {
|
||||
return 'el-icon-close'
|
||||
}
|
||||
if (item.result === 4) {
|
||||
return 'el-icon-remove-outline'
|
||||
}
|
||||
if (item.result === 5) {
|
||||
return 'el-icon-back'
|
||||
if (item.status === 4) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 获得任务对应的颜色 */
|
||||
const getTimelineItemType = (item) => {
|
||||
if (item.result === 1) {
|
||||
const getTaskTimelineItemType = (item: any) => {
|
||||
if ([0, 1, 6, 7].includes(item.status)) {
|
||||
return 'primary'
|
||||
}
|
||||
if (item.result === 2) {
|
||||
if (item.status === 2) {
|
||||
return 'success'
|
||||
}
|
||||
if (item.result === 3) {
|
||||
if (item.status === 3) {
|
||||
return 'danger'
|
||||
}
|
||||
if (item.result === 4) {
|
||||
if (item.status === 4) {
|
||||
return 'info'
|
||||
}
|
||||
if (item.result === 5) {
|
||||
return 'warning'
|
||||
}
|
||||
if (item.result === 6) {
|
||||
return 'default'
|
||||
}
|
||||
if (item.result === 7 || item.result === 8) {
|
||||
if (item.status === 5) {
|
||||
return 'warning'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 子任务
|
||||
*/
|
||||
const processInstanceChildrenTaskList = ref()
|
||||
/** 子任务 */
|
||||
const taskSignListRef = ref()
|
||||
const openChildrenTask = (item: any) => {
|
||||
taskSignListRef.value.open(item)
|
||||
}
|
||||
|
||||
const openChildrenTask = (item) => {
|
||||
processInstanceChildrenTaskList.value.open(item)
|
||||
/** 查看表单 */
|
||||
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
|
||||
const taskForm = ref({
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
}) // 流程任务的表单详情
|
||||
const taskFormVisible = ref(false)
|
||||
const handleFormDetail = async (row) => {
|
||||
// 设置表单
|
||||
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
|
||||
// 弹窗打开
|
||||
taskFormVisible.value = true
|
||||
// 隐藏提交、重置按钮,设置禁用只读
|
||||
await nextTick()
|
||||
fApi.value.fapi.btn.show(false)
|
||||
fApi.value?.fapi?.resetBtn.show(false)
|
||||
fApi.value?.fapi?.disabled(true)
|
||||
}
|
||||
|
||||
/** 刷新数据 */
|
||||
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
<!-- TODO @kyle:需要在讨论下;可能直接选人更合适 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="formData.taskName" disabled placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务标识" prop="taskKey">
|
||||
<el-input v-model="formData.taskKey" disabled placeholder="请输入任务标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="processInstanceName">
|
||||
<el-input v-model="formData.processInstanceName" disabled placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流程标识" prop="processInstanceKey">
|
||||
<el-input v-model="formData.processInstanceKey" disabled placeholder="请输入流程标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则类型" prop="type">
|
||||
<el-select v-model="formData.type" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
|
||||
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 20 || formData.type === 21"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
span="24"
|
||||
>
|
||||
<el-tree-select
|
||||
ref="treeRef"
|
||||
v-model="formData.deptIds"
|
||||
:data="deptTreeOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍后"
|
||||
multiple
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
|
||||
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in postOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
|
||||
label="指定用户"
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="formData.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.nickname"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
|
||||
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
|
||||
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in taskAssignScriptDictDatas"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送原因" prop="reason">
|
||||
<el-input v-model="formData.reason" placeholder="请输入抄送原因" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: Number(undefined),
|
||||
taskName: '',
|
||||
taskKey: '',
|
||||
processInstanceName: '',
|
||||
processInstanceKey: '',
|
||||
startUserId: '',
|
||||
options: [],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: [],
|
||||
userGroupIds: [],
|
||||
scripts: [],
|
||||
reason: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
|
||||
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
|
||||
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
|
||||
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
|
||||
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '抄送原因不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref() // 部门树
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (row) => {
|
||||
// 1. 先重置表单
|
||||
resetForm()
|
||||
// 2. 再设置表单
|
||||
if (row != null) {
|
||||
formData.value.type = undefined as unknown as number
|
||||
formData.value.taskName = row.name
|
||||
formData.value.taskKey = row.id
|
||||
formData.value.processInstanceName = row.processInstance.name
|
||||
formData.value.processInstanceKey = row.processInstance.id
|
||||
formData.value.startUserId = row.processInstance.startUserId
|
||||
}
|
||||
// 打开弹窗
|
||||
dialogVisible.value = true
|
||||
|
||||
// 获得角色列表
|
||||
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
|
||||
// 获得岗位列表
|
||||
postOptions.value = await PostApi.getSimplePostList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 构建表单
|
||||
const form = {
|
||||
...formData.value
|
||||
}
|
||||
// 将 roleIds 等选项赋值到 options 中
|
||||
if (form.type === 10) {
|
||||
form.options = form.roleIds
|
||||
} else if (form.type === 20 || form.type === 21) {
|
||||
form.options = form.deptIds
|
||||
} else if (form.type === 22) {
|
||||
form.options = form.postIds
|
||||
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
|
||||
form.options = form.userIds
|
||||
} else if (form.type === 40) {
|
||||
form.options = form.userGroupIds
|
||||
} else if (form.type === 50) {
|
||||
form.options = form.scripts
|
||||
}
|
||||
form.roleIds = undefined
|
||||
form.deptIds = undefined
|
||||
form.postIds = undefined
|
||||
form.userIds = undefined
|
||||
form.userGroupIds = undefined
|
||||
form.scripts = undefined
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = form as unknown as ProcessInstanceApi.ProcessInstanceCCVO
|
||||
await ProcessInstanceApi.createProcessInstanceCC(data)
|
||||
console.log(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -37,10 +37,12 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
delegateUserId: undefined
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }]
|
||||
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
@@ -79,7 +81,8 @@ const submitForm = async () => {
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
delegateUserId: undefined
|
||||
delegateUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="回退" width="500">
|
||||
<Dialog v-model="dialogVisible" title="回退任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
@@ -7,13 +7,13 @@
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="退回节点" prop="targetDefinitionKey">
|
||||
<el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
|
||||
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
|
||||
<el-select v-model="formData.targetTaskDefinitionKey" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in returnList"
|
||||
:key="item.definitionKey"
|
||||
:key="item.taskDefinitionKey"
|
||||
:label="item.name"
|
||||
:value="item.definitionKey"
|
||||
:value="item.taskDefinitionKey"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -35,19 +35,19 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
targetDefinitionKey: undefined,
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
targetDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
|
||||
targetTaskDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const returnList = ref([])
|
||||
const returnList = ref([] as any)
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
returnList.value = await TaskApi.getReturnList({ taskId: id })
|
||||
returnList.value = await TaskApi.getTaskListByReturn(id)
|
||||
if (returnList.value.length === 0) {
|
||||
message.warning('当前没有可回退的节点')
|
||||
return false
|
||||
@@ -82,7 +82,7 @@ const submitForm = async () => {
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
targetDefinitionKey: undefined,
|
||||
targetTaskDefinitionKey: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
@@ -7,8 +7,8 @@
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="加签处理人" prop="userIdList">
|
||||
<el-select v-model="formData.userIdList" multiple clearable style="width: 100%">
|
||||
<el-form-item label="加签处理人" prop="userIds">
|
||||
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userList"
|
||||
:key="item.id"
|
||||
@@ -36,18 +36,19 @@
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
|
||||
defineOptions({ name: 'TaskSignCreateForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
userIdList: [],
|
||||
type: ''
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
userIdList: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
@@ -75,7 +76,7 @@ const submitForm = async (type: string) => {
|
||||
formLoading.value = true
|
||||
formData.value.type = type
|
||||
try {
|
||||
await TaskApi.taskAddSign(formData.value)
|
||||
await TaskApi.signCreateTask(formData.value)
|
||||
message.success('加签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
@@ -89,8 +90,9 @@ const submitForm = async (type: string) => {
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
userIdList: [],
|
||||
type: ''
|
||||
userIds: [],
|
||||
type: '',
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
@@ -9,8 +9,10 @@
|
||||
>
|
||||
<el-form-item label="减签任务" prop="id">
|
||||
<el-radio-group v-model="formData.id">
|
||||
<el-radio-button v-for="item in subTaskList" :key="item.id" :label="item.id">
|
||||
{{ item.name }}({{ item.assigneeUser.deptName }}{{ item.assigneeUser.nickname }}--审批)
|
||||
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :label="item.id">
|
||||
{{ item.name }}
|
||||
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
|
||||
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
@@ -24,10 +26,12 @@
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||
<script lang="ts" setup>
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'TaskSignDeleteForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
@@ -41,11 +45,11 @@ const formRules = ref({
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const subTaskList = ref([])
|
||||
const childrenTaskList = ref([])
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: string) => {
|
||||
subTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||
if (isEmpty(subTaskList.value)) {
|
||||
childrenTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||
if (isEmpty(childrenTaskList.value)) {
|
||||
message.warning('当前没有可减签的任务')
|
||||
return false
|
||||
}
|
||||
@@ -64,7 +68,7 @@ const submitForm = async () => {
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.taskSubSign(formData.value)
|
||||
await TaskApi.signDeleteTask(formData.value)
|
||||
message.success('减签成功')
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
106
src/views/bpm/processInstance/detail/dialog/TaskSignList.vue
Normal file
106
src/views/bpm/processInstance/detail/dialog/TaskSignList.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="子任务" size="880px">
|
||||
<!-- 当前任务 -->
|
||||
<template #header>
|
||||
<h4>【{{ parentTask.name }} 】审批人:{{ parentTask?.assigneeUser?.nickname }}</h4>
|
||||
<el-button
|
||||
style="margin-left: 5px"
|
||||
v-if="isSignDeleteButtonVisible(parentTask)"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleSignDelete(parentTask)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 子任务列表 -->
|
||||
<el-table :data="parentTask.children" style="width: 100%" row-key="id" border>
|
||||
<el-table-column prop="assigneeUser.nickname" label="审批人" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="assigneeUser.deptName" label="所在部门" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" prop="operation" width="90">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isSignDeleteButtonVisible(scope.row)"
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleSignDelete(scope.row)"
|
||||
>
|
||||
<Icon icon="ep:remove" /> 减签
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 减签 -->
|
||||
<TaskSignDeleteForm ref="taskSignDeleteFormRef" @success="handleSignDeleteSuccess" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import TaskSignDeleteForm from './TaskSignDeleteForm.vue'
|
||||
|
||||
defineOptions({ name: 'TaskSignList' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||
const parentTask = ref({} as any)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (task: any) => {
|
||||
if (isEmpty(task.children)) {
|
||||
message.warning('该任务没有子任务')
|
||||
return
|
||||
}
|
||||
parentTask.value = task
|
||||
// 展开抽屉
|
||||
drawerVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||
|
||||
/** 发起减签 */
|
||||
const taskSignDeleteFormRef = ref()
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const handleSignDelete = (item: any) => {
|
||||
taskSignDeleteFormRef.value.open(item.id)
|
||||
}
|
||||
const handleSignDeleteSuccess = () => {
|
||||
emit('success')
|
||||
// 关闭抽屉
|
||||
drawerVisible.value = false
|
||||
}
|
||||
|
||||
/** 是否显示减签按钮 */
|
||||
const isSignDeleteButtonVisible = (task: any) => {
|
||||
return task && task.children && !isEmpty(task.children)
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="转派审批人" width="500">
|
||||
<Dialog v-model="dialogVisible" title="转派任务" width="500">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
@@ -17,6 +17,9 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转派理由" prop="reason">
|
||||
<el-input v-model="formData.reason" clearable placeholder="请输入转派理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
@@ -28,16 +31,18 @@
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
|
||||
defineOptions({ name: 'TaskTransferForm' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: '',
|
||||
assigneeUserId: undefined
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
})
|
||||
const formRules = ref({
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }]
|
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
@@ -63,7 +68,7 @@ const submitForm = async () => {
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await TaskApi.updateTaskAssignee(formData.value)
|
||||
await TaskApi.transferTask(formData.value)
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
@@ -76,7 +81,8 @@ const submitForm = async () => {
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: '',
|
||||
assigneeUserId: undefined
|
||||
assigneeUserId: undefined,
|
||||
reason: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
@@ -21,9 +21,22 @@
|
||||
{{ processInstance.name }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
|
||||
{{ processInstance.startUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ processInstance.startUser.deptName }}</el-tag>
|
||||
{{ processInstance?.startUser.nickname }}
|
||||
<el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px">
|
||||
<template #header>
|
||||
<span class="el-icon-picture-outline">
|
||||
填写表单【{{ runningTasks[index]?.formName }}】
|
||||
</span>
|
||||
</template>
|
||||
<form-create
|
||||
v-model="approveForms[index].value"
|
||||
v-model:api="approveFormFApis[index]"
|
||||
:option="approveForms[index].option"
|
||||
:rule="approveForms[index].rule"
|
||||
/>
|
||||
</el-card>
|
||||
<el-form-item label="审批建议" prop="reason">
|
||||
<el-input
|
||||
v-model="auditForms[index].reason"
|
||||
@@ -31,6 +44,16 @@
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送人" prop="copyUserIds">
|
||||
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
|
||||
<el-button type="success" @click="handleAudit(item, true)">
|
||||
@@ -69,8 +92,8 @@
|
||||
<!-- 情况一:流程表单 -->
|
||||
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16">
|
||||
<form-create
|
||||
ref="fApi"
|
||||
v-model="detailForm.value"
|
||||
v-model:api="fApi"
|
||||
:option="detailForm.option"
|
||||
:rule="detailForm.rule"
|
||||
/>
|
||||
@@ -82,25 +105,30 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<ProcessInstanceTaskList :loading="tasksLoad" :tasks="tasks" />
|
||||
<ProcessInstanceTaskList
|
||||
:loading="tasksLoad"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
@refresh="getTaskList"
|
||||
/>
|
||||
|
||||
<!-- 高亮流程图 -->
|
||||
<ProcessInstanceBpmnViewer
|
||||
:id="`${id}`"
|
||||
:bpmn-xml="bpmnXML"
|
||||
:bpmn-xml="bpmnXml"
|
||||
:loading="processInstanceLoading"
|
||||
:process-instance="processInstance"
|
||||
:tasks="tasks"
|
||||
/>
|
||||
|
||||
<!-- 弹窗:转派审批人 -->
|
||||
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
|
||||
<!-- 弹窗,回退节点 -->
|
||||
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
|
||||
<!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:回退节点 -->
|
||||
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
|
||||
<!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||
<!-- 加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskAddSignDialogForm ref="taskAddSignDialogForm" @success="getDetail" />
|
||||
<!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -110,14 +138,15 @@ import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue'
|
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||
import TaskReturnDialog from './TaskReturnDialogForm.vue'
|
||||
import TaskDelegateForm from './TaskDelegateForm.vue'
|
||||
import TaskAddSignDialogForm from './TaskAddSignDialogForm.vue'
|
||||
import TaskReturnForm from './dialog/TaskReturnForm.vue'
|
||||
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
|
||||
import TaskTransferForm from './dialog/TaskTransferForm.vue'
|
||||
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
|
||||
import { registerComponent } from '@/utils/routerHelper'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||
|
||||
@@ -126,10 +155,10 @@ const message = useMessage() // 消息弹窗
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
const userId = useUserStore().getUser.id // 当前登录的编号
|
||||
const id = query.id as unknown as number // 流程实例的编号
|
||||
const id = query.id as unknown as string // 流程实例的编号
|
||||
const processInstanceLoading = ref(false) // 流程实例的加载中
|
||||
const processInstance = ref<any>({}) // 流程实例
|
||||
const bpmnXML = ref('') // BPMN XML
|
||||
const bpmnXml = ref('') // BPMN XML
|
||||
const tasksLoad = ref(true) // 任务的加载中
|
||||
const tasks = ref<any[]>([]) // 任务列表
|
||||
// ========== 审批信息 ==========
|
||||
@@ -138,14 +167,30 @@ const auditForms = ref<any[]>([]) // 审批任务的表单
|
||||
const auditRule = reactive({
|
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息
|
||||
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi
|
||||
|
||||
// ========== 申请信息 ==========
|
||||
const fApi = ref<ApiAttrs>() //
|
||||
const detailForm = ref({
|
||||
// 流程表单详情
|
||||
rule: [],
|
||||
option: {},
|
||||
value: {}
|
||||
})
|
||||
}) // 流程实例的表单详情
|
||||
|
||||
/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
|
||||
watch(
|
||||
() => approveFormFApis.value,
|
||||
(value) => {
|
||||
value?.forEach((api) => {
|
||||
api.btn.show(false)
|
||||
api.resetBtn.show(false)
|
||||
})
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
/** 处理审批通过和不通过的操作 */
|
||||
const handleAudit = async (task, pass) => {
|
||||
@@ -161,9 +206,16 @@ const handleAudit = async (task, pass) => {
|
||||
// 2.1 提交审批
|
||||
const data = {
|
||||
id: task.id,
|
||||
reason: auditForms.value[index].reason
|
||||
reason: auditForms.value[index].reason,
|
||||
copyUserIds: auditForms.value[index].copyUserIds
|
||||
}
|
||||
if (pass) {
|
||||
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
const formCreateApi = approveFormFApis.value[index]
|
||||
if (formCreateApi) {
|
||||
await formCreateApi.validate()
|
||||
data.variables = approveForms.value[index].value
|
||||
}
|
||||
await TaskApi.approveTask(data)
|
||||
message.success('审批通过成功')
|
||||
} else {
|
||||
@@ -175,28 +227,27 @@ const handleAudit = async (task, pass) => {
|
||||
}
|
||||
|
||||
/** 转派审批人 */
|
||||
const taskUpdateAssigneeFormRef = ref()
|
||||
const taskTransferFormRef = ref()
|
||||
const openTaskUpdateAssigneeForm = (id: string) => {
|
||||
taskUpdateAssigneeFormRef.value.open(id)
|
||||
taskTransferFormRef.value.open(id)
|
||||
}
|
||||
|
||||
const taskDelegateForm = ref()
|
||||
/** 处理审批退回的操作 */
|
||||
const taskDelegateForm = ref()
|
||||
const handleDelegate = async (task) => {
|
||||
taskDelegateForm.value.open(task.id)
|
||||
}
|
||||
|
||||
//回退弹框组件
|
||||
const taskReturnDialogRef = ref()
|
||||
/** 处理审批退回的操作 */
|
||||
const handleBack = async (task) => {
|
||||
taskReturnDialogRef.value.open(task.id)
|
||||
const taskReturnFormRef = ref()
|
||||
const handleBack = async (task: any) => {
|
||||
taskReturnFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
const taskAddSignDialogForm = ref()
|
||||
/** 处理审批加签的操作 */
|
||||
const handleSign = async (task) => {
|
||||
taskAddSignDialogForm.value.open(task.id)
|
||||
const taskSignCreateFormRef = ref()
|
||||
const handleSign = async (task: any) => {
|
||||
taskSignCreateFormRef.value.open(task.id)
|
||||
}
|
||||
|
||||
/** 获得详情 */
|
||||
@@ -229,9 +280,9 @@ const getProcessInstance = async () => {
|
||||
data.formVariables
|
||||
)
|
||||
nextTick().then(() => {
|
||||
fApi.value?.fapi?.btn.show(false)
|
||||
fApi.value?.fapi?.resetBtn.show(false)
|
||||
fApi.value?.fapi?.disabled(true)
|
||||
fApi.value?.btn.show(false)
|
||||
fApi.value?.resetBtn.show(false)
|
||||
fApi.value?.disabled(true)
|
||||
})
|
||||
} else {
|
||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||
@@ -239,7 +290,9 @@ const getProcessInstance = async () => {
|
||||
}
|
||||
|
||||
// 加载流程图
|
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id as number)
|
||||
bpmnXml.value = (
|
||||
await DefinitionApi.getProcessDefinition(processDefinition.id as number)
|
||||
)?.bpmnXml
|
||||
} finally {
|
||||
processInstanceLoading.value = false
|
||||
}
|
||||
@@ -247,6 +300,10 @@ const getProcessInstance = async () => {
|
||||
|
||||
/** 加载任务列表 */
|
||||
const getTaskList = async () => {
|
||||
runningTasks.value = []
|
||||
auditForms.value = []
|
||||
approveForms.value = []
|
||||
approveFormFApis.value = []
|
||||
try {
|
||||
// 获得未取消的任务
|
||||
tasksLoad.value = true
|
||||
@@ -254,7 +311,7 @@ const getTaskList = async () => {
|
||||
tasks.value = []
|
||||
// 1.1 移除已取消的审批
|
||||
data.forEach((task) => {
|
||||
if (task.result !== 4) {
|
||||
if (task.status !== 4) {
|
||||
tasks.value.push(task)
|
||||
}
|
||||
})
|
||||
@@ -274,8 +331,6 @@ const getTaskList = async () => {
|
||||
})
|
||||
|
||||
// 获得需要自己审批的任务
|
||||
runningTasks.value = []
|
||||
auditForms.value = []
|
||||
loadRunningTask(tasks.value)
|
||||
} finally {
|
||||
tasksLoad.value = false
|
||||
@@ -291,7 +346,7 @@ const loadRunningTask = (tasks) => {
|
||||
loadRunningTask(task.children)
|
||||
}
|
||||
// 2.1 只有待处理才需要
|
||||
if (task.result !== 1 && task.result !== 6) {
|
||||
if (task.status !== 1 && task.status !== 6) {
|
||||
return
|
||||
}
|
||||
// 2.2 自己不是处理人
|
||||
@@ -301,13 +356,26 @@ const loadRunningTask = (tasks) => {
|
||||
// 2.3 添加到处理任务
|
||||
runningTasks.value.push({ ...task })
|
||||
auditForms.value.push({
|
||||
reason: ''
|
||||
reason: '',
|
||||
copyUserIds: []
|
||||
})
|
||||
|
||||
// 2.4 处理 approve 表单
|
||||
if (task.formId && task.formConf) {
|
||||
const approveForm = {}
|
||||
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariable)
|
||||
approveForms.value.push(approveForm)
|
||||
} else {
|
||||
approveForms.value.push({}) // 占位,避免为空
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
getDetail()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
@@ -36,15 +36,20 @@
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
@@ -53,17 +58,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果" prop="result">
|
||||
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="提交时间" prop="createTime">
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
@@ -81,7 +76,7 @@
|
||||
type="primary"
|
||||
plain
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCreate"
|
||||
@click="handleCreate()"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
|
||||
</el-button>
|
||||
@@ -92,34 +87,23 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程编号" align="center" prop="id" width="300px" />
|
||||
<el-table-column label="流程名称" align="center" prop="name" />
|
||||
<el-table-column label="流程分类" align="center" prop="category">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结果" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="提交时间"
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
@@ -130,7 +114,20 @@
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
@@ -143,12 +140,15 @@
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.result === 1"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button link type="primary" v-else @click="handleCreate(scope.row.id)">
|
||||
重新发起
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -163,11 +163,12 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstance' })
|
||||
defineOptions({ name: 'BpmProcessInstanceMy' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
@@ -183,16 +184,16 @@ const queryParams = reactive({
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
result: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getMyProcessInstancePage(queryParams)
|
||||
const data = await ProcessInstanceApi.getProcessInstanceMyPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@@ -213,9 +214,10 @@ const resetQuery = () => {
|
||||
}
|
||||
|
||||
/** 发起流程操作 **/
|
||||
const handleCreate = () => {
|
||||
const handleCreate = (id) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceCreate'
|
||||
name: 'BpmProcessInstanceCreate',
|
||||
query: { processInstanceId: id }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -239,14 +241,20 @@ const handleCancel = async (row) => {
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
})
|
||||
</script>
|
||||
|
||||
255
src/views/bpm/processInstance/manager/index.vue
Normal file
255
src/views/bpm/processInstance/manager/index.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="发起人" prop="startUserId">
|
||||
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入流程名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="category in categoryList"
|
||||
:key="category.code"
|
||||
:label="category.name"
|
||||
:value="category.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发起时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
|
||||
<el-table-column
|
||||
label="流程分类"
|
||||
align="center"
|
||||
prop="categoryName"
|
||||
min-width="100"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
|
||||
<el-table-column label="发起部门" align="center" prop="startUser.deptName" width="120" />
|
||||
<el-table-column label="流程状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="发起时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="169">
|
||||
<template #default="scope">
|
||||
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.status === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { CategoryApi } from '@/api/bpm/category'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { cancelProcessInstanceByAdmin } from '@/api/bpm/processInstance'
|
||||
|
||||
// 它和【我的流程】的差异是,该菜单可以看全部的流程实例
|
||||
defineOptions({ name: 'BpmProcessInstanceManager' })
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
startUserId: undefined,
|
||||
name: '',
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryList = ref([]) // 流程分类列表
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceManagerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 激活时 **/
|
||||
onActivated(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
162
src/views/bpm/processListener/ProcessListenerForm.vue
Normal file
162
src/views/bpm/processListener/ProcessListenerForm.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="110px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select
|
||||
v-model="formData.type"
|
||||
placeholder="请选择类型"
|
||||
@change="formData.event = undefined"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="事件" prop="event">
|
||||
<el-select v-model="formData.event" placeholder="请选择事件">
|
||||
<el-option
|
||||
v-for="event in formData.type == 'execution'
|
||||
? ['start', 'end']
|
||||
: ['create', 'assignment', 'complete', 'delete', 'update', 'timeout']"
|
||||
:label="event"
|
||||
:value="event"
|
||||
:key="event"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="值类型" prop="valueType">
|
||||
<el-select v-model="formData.valueType" placeholder="请选择值类型">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类路径" prop="value" v-if="formData.type == 'class'">
|
||||
<el-input v-model="formData.value" placeholder="请输入类路径" />
|
||||
</el-form-item>
|
||||
<el-form-item label="表达式" prop="value" v-else>
|
||||
<el-input v-model="formData.value" placeholder="请输入表达式" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessListenerForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
event: undefined,
|
||||
valueType: undefined,
|
||||
value: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
event: [{ required: true, message: '监听事件不能为空', trigger: 'blur' }],
|
||||
valueType: [{ required: true, message: '值类型不能为空', trigger: 'change' }],
|
||||
value: [{ required: true, message: '值不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ProcessListenerApi.getProcessListener(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ProcessListenerVO
|
||||
if (formType.value === 'create') {
|
||||
await ProcessListenerApi.createProcessListener(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProcessListenerApi.updateProcessListener(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
event: undefined,
|
||||
valueType: undefined,
|
||||
value: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
185
src/views/bpm/processListener/index.vue
Normal file
185
src/views/bpm/processListener/index.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<doc-alert title="执行监听器、任务监听器" url="https://doc.iocoder.cn/bpm/listener/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="85px"
|
||||
>
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['bpm:process-listener:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="事件" align="center" prop="event" />
|
||||
<el-table-column label="值类型" align="center" prop="valueType">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
|
||||
:value="scope.row.valueType"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="值" align="center" prop="value" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['bpm:process-listener:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['bpm:process-listener:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ProcessListenerForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
|
||||
import ProcessListenerForm from './ProcessListenerForm.vue'
|
||||
|
||||
/** BPM 流程 列表 */
|
||||
defineOptions({ name: 'BpmProcessListener' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ProcessListenerVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
event: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessListenerApi.getProcessListenerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ProcessListenerApi.deleteProcessListener(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
28
src/views/bpm/simpleWorkflow/index.vue
Normal file
28
src/views/bpm/simpleWorkflow/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="dingflow-design">
|
||||
<div class="box-scale">
|
||||
<nodeWrap v-model:nodeConfig="nodeConfig" />
|
||||
<div class="end-node">
|
||||
<div class="end-node-circle"></div>
|
||||
<div class="end-node-text">流程结束</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
|
||||
defineOptions({ name: 'SimpleWorkflowDesignEditor' })
|
||||
let nodeConfig = ref({
|
||||
nodeName: '发起人',
|
||||
type: 0,
|
||||
id: 'root',
|
||||
formPerms: {},
|
||||
nodeUserList: [],
|
||||
childNode: {}
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
|
||||
</style>
|
||||
@@ -1,5 +1,10 @@
|
||||
<!-- 工作流 - 抄送我的流程 -->
|
||||
<template>
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
|
||||
@@ -11,14 +16,6 @@
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属流程" prop="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processInstanceId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
@@ -46,12 +43,17 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="所属流程" prop="processInstanceId" width="300px" />
|
||||
<el-table-column align="center" label="流程名称" prop="processInstanceName" />
|
||||
<el-table-column align="center" label="任务名称" prop="taskName" />
|
||||
<el-table-column align="center" label="流程发起人" prop="startUserNickname" />
|
||||
<el-table-column align="center" label="抄送发起人" prop="creatorNickname" />
|
||||
<el-table-column align="center" label="抄送原因" prop="reason" />
|
||||
<el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
|
||||
<el-table-column align="center" label="流程发起人" prop="startUserName" min-width="100" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="流程发起时间"
|
||||
prop="processInstanceStartTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="抄送任务" prop="taskName" min-width="180" />
|
||||
<el-table-column align="center" label="抄送人" prop="creatorName" min-width="100" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="抄送时间"
|
||||
@@ -59,9 +61,9 @@
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">跳转待办</el-button>
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -78,14 +80,14 @@
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
defineOptions({ name: 'BpmCCProcessInstance' })
|
||||
defineOptions({ name: 'BpmProcessInstanceCopy' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = ref({
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
processInstanceId: '',
|
||||
@@ -98,7 +100,7 @@ const queryFormRef = ref() // 搜索的表单
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getProcessInstanceCCPage(queryParams)
|
||||
const data = await ProcessInstanceApi.getProcessInstanceCopyPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@@ -118,7 +120,7 @@ const handleAudit = (row: any) => {
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNo = 1
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="任务编号" min-width="120">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务名称">
|
||||
{{ detailData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属流程">
|
||||
{{ detailData.processInstance.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="流程发起人">
|
||||
{{ detailData.processInstance.startUserNickname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="detailData.result" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原因">
|
||||
{{ detailData.reason }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
defineOptions({ name: 'BpmTaskDetail' })
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref() // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: TaskApi.TaskVO) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = data
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
@@ -46,27 +52,51 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="任务编号" prop="id" width="300px" />
|
||||
<el-table-column align="center" label="任务名称" prop="name" />
|
||||
<el-table-column align="center" label="所属流程" prop="processInstance.name" />
|
||||
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" />
|
||||
<el-table-column align="center" label="状态" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="原因" prop="reason" />
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作">
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务开始时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务结束时间"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">流程</el-button>
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="180" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -78,15 +108,11 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:详情 -->
|
||||
<TaskDetail ref="detailRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import TaskDetail from './TaskDetail.vue'
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
@@ -107,7 +133,7 @@ const queryFormRef = ref() // 搜索的表单
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getDoneTaskPage(queryParams)
|
||||
const data = await TaskApi.getTaskDonePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@@ -127,14 +153,8 @@ const resetQuery = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (row: TaskApi.TaskVO) => {
|
||||
detailRef.value.open(row)
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row) => {
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
|
||||
166
src/views/bpm/task/manager/index.vue
Normal file
166
src/views/bpm/task/manager/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="任务名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入任务名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务开始时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务结束时间"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="审批人" prop="assigneeUser.nickname" width="100" />
|
||||
<el-table-column align="center" label="审批状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="180" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
// 它和【待办任务】【已办任务】的差异是,该菜单可以看全部的流程任务
|
||||
defineOptions({ name: 'BpmManagerTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTaskManagerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
@@ -46,27 +52,33 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="任务编号" prop="id" width="300px" />
|
||||
<el-table-column align="center" label="任务名称" prop="name" />
|
||||
<el-table-column align="center" label="所属流程" prop="processInstance.name" />
|
||||
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" />
|
||||
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
|
||||
<el-table-column
|
||||
align="center"
|
||||
label="发起人"
|
||||
prop="processInstance.startUser.nickname"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
label="发起时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="任务状态" prop="suspensionState">
|
||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="任务时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="流程编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
|
||||
<el-table-column align="center" label="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.suspensionState === 1" type="success">激活</el-tag>
|
||||
<el-tag v-if="scope.row.suspensionState === 2" type="warning">挂起</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">审批进度</el-button>
|
||||
<el-button link type="primary" @click="handleCC(scope.row)">抄送</el-button>
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">办理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -77,16 +89,14 @@
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<TaskCCDialogForm ref="taskCCDialogForm" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
import TaskCCDialogForm from '../../processInstance/detail/TaskCCDialogForm.vue'
|
||||
|
||||
defineOptions({ name: 'BpmDoneTask' })
|
||||
defineOptions({ name: 'BpmTodoTask' })
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
@@ -105,7 +115,7 @@ const queryFormRef = ref() // 搜索的表单
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTodoTaskPage(queryParams)
|
||||
const data = await TaskApi.getTaskTodoPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
@@ -126,7 +136,7 @@ const resetQuery = () => {
|
||||
}
|
||||
|
||||
/** 处理审批按钮 */
|
||||
const handleAudit = (row) => {
|
||||
const handleAudit = (row: any) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
@@ -135,12 +145,6 @@ const handleAudit = (row) => {
|
||||
})
|
||||
}
|
||||
|
||||
const taskCCDialogForm = ref()
|
||||
/** 处理抄送按钮 */
|
||||
const handleCC = (row) => {
|
||||
taskCCDialogForm.value.open(row)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="任务名称" prop="taskDefinitionName">
|
||||
<el-input v-model="formData.taskDefinitionName" disabled placeholder="请输入流标标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务标识" prop="taskDefinitionKey">
|
||||
<el-input v-model="formData.taskDefinitionKey" disabled placeholder="请输入任务标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规则类型" prop="type">
|
||||
<el-select v-model="formData.type" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
|
||||
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 20 || formData.type === 21"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
span="24"
|
||||
>
|
||||
<el-tree-select
|
||||
ref="treeRef"
|
||||
v-model="formData.deptIds"
|
||||
:data="deptTreeOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍后"
|
||||
multiple
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
|
||||
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in postOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
|
||||
label="指定用户"
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="formData.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.nickname"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
|
||||
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:key="parseInt(item.id)"
|
||||
:label="item.name"
|
||||
:value="parseInt(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
|
||||
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in taskAssignScriptDictDatas"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
|
||||
defineOptions({ name: 'BpmTaskAssignRuleForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: Number(undefined),
|
||||
modelId: '',
|
||||
options: [],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: [],
|
||||
userGroupIds: [],
|
||||
scripts: []
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
|
||||
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
|
||||
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
|
||||
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
|
||||
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const deptTreeOptions = ref() // 部门树
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (modelId: string, row: TaskAssignRuleApi.TaskAssignVO) => {
|
||||
// 1. 先重置表单
|
||||
resetForm()
|
||||
// 2. 再设置表单
|
||||
formData.value = {
|
||||
...row,
|
||||
modelId: modelId,
|
||||
options: [],
|
||||
roleIds: [],
|
||||
deptIds: [],
|
||||
postIds: [],
|
||||
userIds: [],
|
||||
userGroupIds: [],
|
||||
scripts: []
|
||||
}
|
||||
// 将 options 赋值到对应的 roleIds 等选项
|
||||
if (row.type === 10) {
|
||||
formData.value.roleIds.push(...row.options)
|
||||
} else if (row.type === 20 || row.type === 21) {
|
||||
formData.value.deptIds.push(...row.options)
|
||||
} else if (row.type === 22) {
|
||||
formData.value.postIds.push(...row.options)
|
||||
} else if (row.type === 30 || row.type === 31 || row.type === 32) {
|
||||
formData.value.userIds.push(...row.options)
|
||||
} else if (row.type === 40) {
|
||||
formData.value.userGroupIds.push(...row.options)
|
||||
} else if (row.type === 50) {
|
||||
formData.value.scripts.push(...row.options)
|
||||
}
|
||||
// 打开弹窗
|
||||
dialogVisible.value = true
|
||||
|
||||
// 获得角色列表
|
||||
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
|
||||
// 获得岗位列表
|
||||
postOptions.value = await PostApi.getSimplePostList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 构建表单
|
||||
const form = {
|
||||
...formData.value,
|
||||
taskDefinitionName: undefined
|
||||
}
|
||||
// 将 roleIds 等选项赋值到 options 中
|
||||
if (form.type === 10) {
|
||||
form.options = form.roleIds
|
||||
} else if (form.type === 20 || form.type === 21) {
|
||||
form.options = form.deptIds
|
||||
} else if (form.type === 22) {
|
||||
form.options = form.postIds
|
||||
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
|
||||
form.options = form.userIds
|
||||
} else if (form.type === 40) {
|
||||
form.options = form.userGroupIds
|
||||
} else if (form.type === 50) {
|
||||
form.options = form.scripts
|
||||
}
|
||||
form.roleIds = undefined
|
||||
form.deptIds = undefined
|
||||
form.postIds = undefined
|
||||
form.userIds = undefined
|
||||
form.userGroupIds = undefined
|
||||
form.scripts = undefined
|
||||
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = form as unknown as TaskAssignRuleApi.TaskAssignVO
|
||||
if (!data.id) {
|
||||
await TaskAssignRuleApi.createTaskAssignRule(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await TaskAssignRuleApi.updateTaskAssignRule(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="任务名" align="center" prop="taskDefinitionName" />
|
||||
<el-table-column label="任务标识" align="center" prop="taskDefinitionKey" />
|
||||
<el-table-column label="规则类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规则范围" align="center" prop="options">
|
||||
<template #default="scope">
|
||||
<el-tag class="mr-5px" :key="option" v-for="option in scope.row.options">
|
||||
{{ getAssignRuleOptionName(scope.row.type, option) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="queryParams.modelId" label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm(scope.row)"
|
||||
v-hasPermi="['bpm:task-assign-rule:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
<!-- 添加/修改弹窗 -->
|
||||
<TaskAssignRuleForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
import TaskAssignRuleForm from './TaskAssignRuleForm.vue'
|
||||
|
||||
defineOptions({ name: 'BpmTaskAssignRule' })
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
modelId: query.modelId, // 流程模型的编号。如果 modelId 非空,则用于流程模型的查看与配置
|
||||
processDefinitionId: query.processDefinitionId // 流程定义的编号。如果 processDefinitionId 非空,则用于流程定义的查看,不支持配置
|
||||
})
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
|
||||
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = await TaskAssignRuleApi.getTaskAssignRuleList(queryParams)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 翻译规则范围 */
|
||||
// TODO 芋艿:各种 ts 报错
|
||||
const getAssignRuleOptionName = (type, option) => {
|
||||
if (type === 10) {
|
||||
for (const roleOption of roleOptions.value) {
|
||||
if (roleOption.id === option) {
|
||||
return roleOption.name
|
||||
}
|
||||
}
|
||||
} else if (type === 20 || type === 21) {
|
||||
for (const deptOption of deptOptions.value) {
|
||||
if (deptOption.id === option) {
|
||||
return deptOption.name
|
||||
}
|
||||
}
|
||||
} else if (type === 22) {
|
||||
for (const postOption of postOptions.value) {
|
||||
if (postOption.id === option) {
|
||||
return postOption.name
|
||||
}
|
||||
}
|
||||
} else if (type === 30 || type === 31 || type === 32) {
|
||||
for (const userOption of userOptions.value) {
|
||||
if (userOption.id === option) {
|
||||
return userOption.nickname
|
||||
}
|
||||
}
|
||||
} else if (type === 40) {
|
||||
for (const userGroupOption of userGroupOptions.value) {
|
||||
if (userGroupOption.id === option) {
|
||||
return userGroupOption.name
|
||||
}
|
||||
}
|
||||
} else if (type === 50) {
|
||||
option = option + '' // 转换成 string
|
||||
for (const dictData of taskAssignScriptDictDatas) {
|
||||
if (dictData.value === option) {
|
||||
return dictData.label
|
||||
}
|
||||
}
|
||||
}
|
||||
return '未知(' + option + ')'
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (row: TaskAssignRuleApi.TaskAssignVO) => {
|
||||
formRef.value.open(queryParams.modelId, row)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 获得角色列表
|
||||
roleOptions.value = await RoleApi.getSimpleRoleList()
|
||||
// 获得部门列表
|
||||
deptOptions.value = await DeptApi.getSimpleDeptList()
|
||||
// 获得岗位列表
|
||||
postOptions.value = await PostApi.getSimplePostList()
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
|
||||
})
|
||||
</script>
|
||||
@@ -64,7 +64,7 @@ import BusinessDetailsHeader from './BusinessDetailsHeader.vue'
|
||||
import BusinessDetailsInfo from './BusinessDetailsInfo.vue'
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import BusinessForm from '@/views/crm/business/BusinessForm.vue'
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
@@ -113,7 +113,7 @@ const transfer = () => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contactId: number) => {
|
||||
if (!contactId) {
|
||||
return
|
||||
|
||||
@@ -57,7 +57,7 @@ import PermissionList from '@/views/crm/permission/components/PermissionList.vue
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import type { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import type { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
|
||||
defineOptions({ name: 'CrmClueDetail' })
|
||||
@@ -103,7 +103,7 @@ const handleTransform = async () => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async () => {
|
||||
const data = await getOperateLogPage({
|
||||
bizType: BizTypeEnum.CRM_CLUE,
|
||||
|
||||
@@ -49,7 +49,7 @@ import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vu
|
||||
import BusinessList from '@/views/crm/business/components/BusinessList.vue' // 商机列表
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import ContactForm from '@/views/crm/contact/ContactForm.vue'
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
@@ -88,7 +88,7 @@ const transfer = () => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contactId: number) => {
|
||||
if (!contactId) {
|
||||
return
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import * as ContractApi from '@/api/crm/contract'
|
||||
import ContractDetailsInfo from './ContractDetailsInfo.vue'
|
||||
import ContractDetailsHeader from './ContractDetailsHeader.vue'
|
||||
@@ -94,7 +94,7 @@ const getContractData = async () => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (contractId: number) => {
|
||||
if (!contractId) {
|
||||
return
|
||||
|
||||
@@ -93,7 +93,7 @@ import PermissionList from '@/views/crm/permission/components/PermissionList.vue
|
||||
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
|
||||
import FollowUpList from '@/views/crm/followup/index.vue'
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import type { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import type { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import CustomerDistributeForm from '@/views/crm/customer/pool/CustomerDistributeForm.vue'
|
||||
|
||||
@@ -185,7 +185,7 @@ const handlePutPool = async () => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async () => {
|
||||
if (!customerId.value) {
|
||||
return
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import * as ProductApi from '@/api/crm/product'
|
||||
import ProductDetailsHeader from '@/views/crm/product/detail/ProductDetailsHeader.vue'
|
||||
import ProductDetailsInfo from '@/views/crm/product/detail/ProductDetailsInfo.vue'
|
||||
@@ -40,7 +40,7 @@ const getProductData = async (id: number) => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (productId: number) => {
|
||||
if (!productId) {
|
||||
return
|
||||
|
||||
@@ -34,7 +34,7 @@ import ReceivableDetailsHeader from './ReceivableDetailsHeader.vue'
|
||||
import ReceivableDetailsInfo from './ReceivableDetailsInfo.vue'
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import ReceivableForm from '@/views/crm/receivable/ReceivableForm.vue'
|
||||
|
||||
@@ -66,7 +66,7 @@ const openForm = (type: string, id?: number) => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (receivableId: number) => {
|
||||
if (!receivableId) {
|
||||
return
|
||||
|
||||
@@ -37,7 +37,7 @@ import ReceivablePlanDetailsHeader from './ReceivablePlanDetailsHeader.vue'
|
||||
import ReceivablePlanDetailsInfo from './ReceivablePlanDetailsInfo.vue'
|
||||
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
|
||||
import { BizTypeEnum } from '@/api/crm/permission'
|
||||
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||
import { OperateLogVO } from '@/api/system/operatelog'
|
||||
import { getOperateLogPage } from '@/api/crm/operateLog'
|
||||
import ReceivablePlanForm from '@/views/crm/receivable/plan/ReceivablePlanForm.vue'
|
||||
|
||||
@@ -70,7 +70,7 @@ const openForm = (type: string, id?: number) => {
|
||||
}
|
||||
|
||||
/** 获取操作日志 */
|
||||
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||
const logList = ref<OperateLogVO[]>([]) // 操作日志列表
|
||||
const getOperateLog = async (receivablePlanId: number) => {
|
||||
if (!receivablePlanId) {
|
||||
return
|
||||
|
||||
@@ -10,11 +10,39 @@
|
||||
<!-- 统计列表 -->
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||
<el-table-column label="客户名称" align="center" prop="customerName" min-width="200" />
|
||||
<el-table-column label="序号" align="center" type="index" width="80" fixed="left" />
|
||||
<el-table-column
|
||||
label="客户名称"
|
||||
align="center"
|
||||
prop="customerName"
|
||||
min-width="200"
|
||||
fixed="left"
|
||||
/>
|
||||
<el-table-column label="合同名称" align="center" prop="contractName" min-width="200" />
|
||||
<el-table-column label="合同总金额" align="center" prop="totalPrice" min-width="200" />
|
||||
<el-table-column label="回款金额" align="center" prop="receivablePrice" min-width="200" />
|
||||
<el-table-column
|
||||
label="合同总金额"
|
||||
align="center"
|
||||
prop="totalPrice"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="回款金额"
|
||||
align="center"
|
||||
prop="receivablePrice"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="负责人" align="center" prop="ownerUserName" min-width="200" />
|
||||
<el-table-column label="创建人" align="center" prop="creatorUserName" min-width="200" />
|
||||
<el-table-column
|
||||
@@ -28,8 +56,9 @@
|
||||
label="下单日期"
|
||||
align="center"
|
||||
prop="orderDate"
|
||||
:formatter="dateFormatter2"
|
||||
:formatter="dateFormatter"
|
||||
min-width="200"
|
||||
fixed="right"
|
||||
/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
@@ -41,7 +70,9 @@ import {
|
||||
} from '@/api/crm/statistics/customer'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { round } from 'lodash-es'
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
defineOptions({ name: 'CustomerConversionStat' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
@@ -97,6 +128,7 @@ const echartsOption = reactive<EChartsOption>({
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
// TODO @ddhb52:这里调用 StatisticsCustomerApi.getCustomerSummaryByDate 好像不太对???
|
||||
const customerCount = await StatisticsCustomerApi.getCustomerSummaryByDate(props.queryParams)
|
||||
const contractSummary = await StatisticsCustomerApi.getContractSummary(props.queryParams)
|
||||
// 2.1 更新 Echarts 数据
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||
<el-table-column label="员工姓名" align="center" prop="ownerUserName" min-width="200" />
|
||||
<el-table-column label="跟进次数" align="right" prop="followupRecordCount" min-width="200" />
|
||||
<el-table-column label="跟进次数" align="right" prop="followUpRecordCount" min-width="200" />
|
||||
<el-table-column
|
||||
label="跟进客户数"
|
||||
align="right"
|
||||
prop="followupCustomerCount"
|
||||
prop="followUpCustomerCount"
|
||||
min-width="200"
|
||||
/>
|
||||
</el-table>
|
||||
@@ -25,8 +25,8 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
StatisticsCustomerApi,
|
||||
CrmStatisticsFollowupSummaryByDateRespVO,
|
||||
CrmStatisticsFollowupSummaryByUserRespVO
|
||||
CrmStatisticsFollowUpSummaryByDateRespVO,
|
||||
CrmStatisticsFollowUpSummaryByUserRespVO
|
||||
} from '@/api/crm/statistics/customer'
|
||||
import { EChartsOption } from 'echarts'
|
||||
|
||||
@@ -34,7 +34,7 @@ defineOptions({ name: 'CustomerFollowupSummary' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<CrmStatisticsFollowupSummaryByUserRespVO[]>([]) // 列表的数据
|
||||
const list = ref<CrmStatisticsFollowUpSummaryByUserRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 柱状图配置:纵向 */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
@@ -89,30 +89,30 @@ const echartsOption = reactive<EChartsOption>({
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const followupSummaryByDate = await StatisticsCustomerApi.getFollowupSummaryByDate(
|
||||
const followUpSummaryByDate = await StatisticsCustomerApi.getFollowUpSummaryByDate(
|
||||
props.queryParams
|
||||
)
|
||||
const followupSummaryByUser = await StatisticsCustomerApi.getFollowupSummaryByUser(
|
||||
const followUpSummaryByUser = await StatisticsCustomerApi.getFollowUpSummaryByUser(
|
||||
props.queryParams
|
||||
)
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.xAxis && echartsOption.xAxis['data']) {
|
||||
echartsOption.xAxis['data'] = followupSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowupSummaryByDateRespVO) => s.time
|
||||
echartsOption.xAxis['data'] = followUpSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowUpSummaryByDateRespVO) => s.time
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = followupSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowupSummaryByDateRespVO) => s.followupCustomerCount
|
||||
echartsOption.series[0]['data'] = followUpSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowUpSummaryByDateRespVO) => s.followUpCustomerCount
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
|
||||
echartsOption.series[1]['data'] = followupSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowupSummaryByDateRespVO) => s.followupRecordCount
|
||||
echartsOption.series[1]['data'] = followUpSummaryByDate.map(
|
||||
(s: CrmStatisticsFollowUpSummaryByDateRespVO) => s.followUpRecordCount
|
||||
)
|
||||
}
|
||||
// 2.2 更新列表数据
|
||||
list.value = followupSummaryByUser
|
||||
list.value = followUpSummaryByUser
|
||||
loading.value = false
|
||||
}
|
||||
defineExpose({ loadData })
|
||||
@@ -11,8 +11,12 @@
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||
<el-table-column label="跟进方式" align="center" prop="followupType" min-width="200" />
|
||||
<el-table-column label="个数" align="center" prop="followupRecordCount" min-width="200" />
|
||||
<el-table-column label="跟进方式" align="center" prop="followUpType" min-width="200">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="scope.row.followUpType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="个数" align="center" prop="followUpRecordCount" min-width="200" />
|
||||
<el-table-column label="占比(%)" align="center" prop="portion" min-width="200" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
@@ -20,16 +24,18 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
StatisticsCustomerApi,
|
||||
CrmStatisticsFollowupSummaryByTypeRespVO
|
||||
CrmStatisticsFollowUpSummaryByTypeRespVO
|
||||
} from '@/api/crm/statistics/customer'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { round, sumBy } from 'lodash-es'
|
||||
import { sumBy } from 'lodash-es'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { erpCalculatePercentage } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'CustomerFollowupType' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<CrmStatisticsFollowupSummaryByTypeRespVO[]>([]) // 列表的数据
|
||||
const list = ref<CrmStatisticsFollowUpSummaryByTypeRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 饼图配置 */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
@@ -71,27 +77,26 @@ const echartsOption = reactive<EChartsOption>({
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const followupSummaryByType = await StatisticsCustomerApi.getFollowupSummaryByType(
|
||||
const followUpSummaryByType = await StatisticsCustomerApi.getFollowUpSummaryByType(
|
||||
props.queryParams
|
||||
)
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = followupSummaryByType.map(
|
||||
(r: CrmStatisticsFollowupSummaryByTypeRespVO) => {
|
||||
echartsOption.series[0]['data'] = followUpSummaryByType.map(
|
||||
(row: CrmStatisticsFollowUpSummaryByTypeRespVO) => {
|
||||
return {
|
||||
name: r.followupType,
|
||||
value: r.followupRecordCount
|
||||
name: getDictLabel(DICT_TYPE.CRM_FOLLOW_UP_TYPE, row.followUpType),
|
||||
value: row.followUpRecordCount
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
// 2.2 更新列表数据
|
||||
const totalCount = sumBy(followupSummaryByType, 'followupRecordCount')
|
||||
list.value = followupSummaryByType.map((r: CrmStatisticsFollowupSummaryByTypeRespVO) => {
|
||||
const totalCount = sumBy(followUpSummaryByType, 'followUpRecordCount')
|
||||
list.value = followUpSummaryByType.map((row: CrmStatisticsFollowUpSummaryByTypeRespVO) => {
|
||||
return {
|
||||
followupType: r.followupType,
|
||||
followupRecordCount: r.followupRecordCount,
|
||||
portion: round((r.followupRecordCount / totalCount) * 100, 2)
|
||||
...row,
|
||||
portion: erpCalculatePercentage(row.followUpRecordCount, totalCount)
|
||||
}
|
||||
})
|
||||
loading.value = false
|
||||
@@ -10,8 +10,8 @@
|
||||
<!-- 统计列表 -->
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||
<el-table-column label="员工姓名" prop="ownerUserName" min-width="100" />
|
||||
<el-table-column label="序号" align="center" type="index" width="80" fixed="left" />
|
||||
<el-table-column label="员工姓名" prop="ownerUserName" min-width="100" fixed="left" />
|
||||
<el-table-column
|
||||
label="新增客户数"
|
||||
align="right"
|
||||
@@ -21,28 +21,31 @@
|
||||
<el-table-column label="成交客户数" align="right" prop="customerDealCount" min-width="200" />
|
||||
<el-table-column label="客户成交率(%)" align="right" min-width="200">
|
||||
<template #default="scope">
|
||||
{{
|
||||
scope.row.customerCreateCount !== 0
|
||||
? round((scope.row.customerDealCount / scope.row.customerCreateCount) * 100, 2)
|
||||
: 0
|
||||
}}
|
||||
{{ erpCalculatePercentage(scope.row.customerDealCount, scope.row.customerCreateCount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同总金额" align="right" prop="contractPrice" min-width="200" />
|
||||
<el-table-column label="回款金额" align="right" prop="receivablePrice" min-width="200" />
|
||||
<el-table-column
|
||||
label="合同总金额"
|
||||
align="right"
|
||||
prop="contractPrice"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="回款金额"
|
||||
align="right"
|
||||
prop="receivablePrice"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
<el-table-column label="未回款金额" align="right" min-width="200">
|
||||
<!-- TODO @dhb52:参考 util/index.ts 的 // ========== ERP 专属方法 ========== 部分,搞个两个方法,一个格式化百分比,一个计算百分比 -->
|
||||
<template #default="scope">
|
||||
{{ round(scope.row.contractPrice - scope.row.receivablePrice, 2) }}
|
||||
{{ erpCalculatePercentage(scope.row.receivablePrice, scope.row.contractPrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回款完成率(%)" align="right" min-width="200">
|
||||
<el-table-column label="回款完成率(%)" align="right" min-width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
{{
|
||||
scope.row.contractPrice !== 0
|
||||
? round((scope.row.receivablePrice / scope.row.contractPrice) * 100, 2)
|
||||
: 0
|
||||
}}
|
||||
{{ erpCalculatePercentage(scope.row.receivablePrice, scope.row.contractPrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -55,7 +58,7 @@ import {
|
||||
CrmStatisticsCustomerSummaryByUserRespVO
|
||||
} from '@/api/crm/statistics/customer'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { round } from 'lodash-es'
|
||||
import { erpCalculatePercentage, erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'CustomerSummary' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
@@ -3,49 +3,65 @@
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="时间范围" prop="orderDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.times"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间间隔" prop="interval">
|
||||
<el-select v-model="queryParams.interval" class="!w-240px" placeholder="间隔类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.DATE_INTERVAL)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.deptId"
|
||||
class="!w-240px"
|
||||
:data="deptList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="!w-240px"
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
@change="queryParams.userId = undefined"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="userId">
|
||||
<el-select v-model="queryParams.userId" class="!w-240px" placeholder="员工" clearable>
|
||||
<el-select v-model="queryParams.userId" class="!w-240px" clearable placeholder="员工">
|
||||
<el-option
|
||||
v-for="(user, index) in userListByDeptId"
|
||||
:key="index"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button>
|
||||
<el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@@ -54,24 +70,24 @@
|
||||
<el-col>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 客户总量分析 -->
|
||||
<el-tab-pane label="客户总量分析" name="customerSummary" lazy>
|
||||
<CustomerSummary :query-params="queryParams" ref="customerSummaryRef" />
|
||||
<el-tab-pane label="客户总量分析" lazy name="customerSummary">
|
||||
<CustomerSummary ref="customerSummaryRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户跟进次数分析 -->
|
||||
<el-tab-pane label="客户跟进次数分析" name="followupSummary" lazy>
|
||||
<CustomerFollowupSummary :query-params="queryParams" ref="followupSummaryRef" />
|
||||
<el-tab-pane label="客户跟进次数分析" lazy name="followUpSummary">
|
||||
<CustomerFollowUpSummary ref="followUpSummaryRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户跟进方式分析 -->
|
||||
<el-tab-pane label="客户跟进方式分析" name="followupType" lazy>
|
||||
<CustomerFollowupType :query-params="queryParams" ref="followupTypeRef" />
|
||||
<el-tab-pane label="客户跟进方式分析" lazy name="followUpType">
|
||||
<CustomerFollowUpType ref="followUpTypeRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户转化率分析 -->
|
||||
<el-tab-pane label="客户转化率分析" name="conversionStat" lazy>
|
||||
<CustomerConversionStat :query-params="queryParams" ref="conversionStatRef" />
|
||||
<el-tab-pane label="客户转化率分析" lazy name="conversionStat">
|
||||
<CustomerConversionStat ref="conversionStatRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 成交周期分析 -->
|
||||
<el-tab-pane label="成交周期分析" name="dealCycle" lazy>
|
||||
<CustomerDealCycle :query-params="queryParams" ref="dealCycleRef" />
|
||||
<el-tab-pane label="成交周期分析" lazy name="dealCycle">
|
||||
<CustomerDealCycle ref="dealCycleRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
@@ -84,14 +100,16 @@ import { useUserStore } from '@/store/modules/user'
|
||||
import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import CustomerSummary from './components/CustomerSummary.vue'
|
||||
import CustomerFollowupSummary from './components/CustomerFollowupSummary.vue'
|
||||
import CustomerFollowupType from './components/CustomerFollowupType.vue'
|
||||
import CustomerFollowUpSummary from './components/CustomerFollowUpSummary.vue'
|
||||
import CustomerFollowUpType from './components/CustomerFollowUpType.vue'
|
||||
import CustomerConversionStat from './components/CustomerConversionStat.vue'
|
||||
import CustomerDealCycle from './components/CustomerDealCycle.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
|
||||
defineOptions({ name: 'CrmStatisticsCustomer' })
|
||||
|
||||
const queryParams = reactive({
|
||||
interval: 1,
|
||||
deptId: useUserStore().getUser.deptId,
|
||||
userId: undefined,
|
||||
times: [
|
||||
@@ -104,50 +122,45 @@ const queryParams = reactive({
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const deptList = ref<Tree[]>([]) // 部门树形结构
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 全量用户清单
|
||||
// 根据选择的部门筛选员工清单
|
||||
|
||||
/** 根据选择的部门筛选员工清单 */
|
||||
const userListByDeptId = computed(() =>
|
||||
queryParams.deptId
|
||||
? userList.value.filter((u: UserApi.UserVO) => u.deptId === queryParams.deptId)
|
||||
: []
|
||||
)
|
||||
|
||||
// 活跃标签
|
||||
const activeTab = ref('customerSummary')
|
||||
// 1.客户总量分析
|
||||
const customerSummaryRef = ref()
|
||||
// 2.客户跟进次数分析
|
||||
const followupSummaryRef = ref()
|
||||
// 3.客户跟进方式分析
|
||||
const followupTypeRef = ref()
|
||||
// 4.客户转化率分析
|
||||
const conversionStatRef = ref()
|
||||
// 5.公海客户分析
|
||||
// 缺 crm_owner_record 表
|
||||
// 6.成交周期分析
|
||||
const dealCycleRef = ref()
|
||||
const activeTab = ref('customerSummary') // 活跃标签
|
||||
const customerSummaryRef = ref() // 1. 客户总量分析
|
||||
const followUpSummaryRef = ref() // 2. 客户跟进次数分析
|
||||
const followUpTypeRef = ref() // 3. 客户跟进方式分析
|
||||
const conversionStatRef = ref() // 4. 客户转化率分析
|
||||
// 5. TODO 公海客户分析
|
||||
// 缺 crm_owner_record 表 TODO @dhb52:可以先做界面 + 接口,接口数据直接写死返回,相当于 mock 出来
|
||||
const dealCycleRef = ref() // 6. 成交周期分析
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
switch (activeTab.value) {
|
||||
case 'customerSummary':
|
||||
case 'customerSummary': // 客户总量分析
|
||||
customerSummaryRef.value?.loadData?.()
|
||||
break
|
||||
case 'followupSummary':
|
||||
followupSummaryRef.value?.loadData?.()
|
||||
case 'followUpSummary': // 客户跟进次数分析
|
||||
followUpSummaryRef.value?.loadData?.()
|
||||
break
|
||||
case 'followupType':
|
||||
followupTypeRef.value?.loadData?.()
|
||||
case 'followUpType': // 客户跟进方式分析
|
||||
followUpTypeRef.value?.loadData?.()
|
||||
break
|
||||
case 'conversionStat':
|
||||
case 'conversionStat': // 客户转化率分析
|
||||
conversionStatRef.value?.loadData?.()
|
||||
break
|
||||
case 'dealCycle':
|
||||
case 'dealCycle': // 成交周期分析
|
||||
dealCycleRef.value?.loadData?.()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 当 activeTab 改变时,刷新当前活动的 tab
|
||||
/** 当 activeTab 改变时,刷新当前活动的 tab */
|
||||
watch(activeTab, () => {
|
||||
handleQuery()
|
||||
})
|
||||
@@ -158,7 +171,7 @@ const resetQuery = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 加载部门树
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
userList.value = handleTree(await UserApi.getSimpleUserList())
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<!-- 客户总量统计 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 TODO @scholar:统计列表的展示不对 -->
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="tableData">
|
||||
<el-table-column
|
||||
v-for="item in columnsData"
|
||||
:key="item.prop"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row[item.prop] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { EChartsOption } from 'echarts'
|
||||
import {
|
||||
StatisticsPerformanceApi,
|
||||
StatisticsPerformanceRespVO
|
||||
} from '@/api/crm/statistics/performance'
|
||||
|
||||
defineOptions({ name: 'ContractCountPerformance' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<StatisticsPerformanceRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 柱状图配置:纵向 */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true
|
||||
},
|
||||
legend: {},
|
||||
series: [
|
||||
{
|
||||
name: '当月合同数量(个)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '上月合同数量(个)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '去年同月合同数量(个)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '同比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '环比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataZoom: {
|
||||
xAxisIndex: false // 数据区域缩放:Y 轴不缩放
|
||||
},
|
||||
brush: {
|
||||
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
|
||||
},
|
||||
saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '数量(个)',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '',
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}%'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name: '日期',
|
||||
data: []
|
||||
}
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const performanceList = await StatisticsPerformanceApi.getContractCountPerformance(
|
||||
props.queryParams
|
||||
)
|
||||
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.xAxis && echartsOption.xAxis['data']) {
|
||||
echartsOption.xAxis['data'] = performanceList.map((s: StatisticsPerformanceRespVO) => s.time)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.currentMonthCount
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
|
||||
echartsOption.series[1]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastMonthCount
|
||||
)
|
||||
echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) {
|
||||
echartsOption.series[2]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastYearCount
|
||||
)
|
||||
echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
|
||||
// 2.2 更新列表数据
|
||||
list.value = performanceList
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const columnsData = reactive([])
|
||||
const tableData = reactive([
|
||||
{ title: '当月合同数量统计(个)' },
|
||||
{ title: '上月合同数量统计(个)' },
|
||||
{ title: '去年当月合同数量统计(个)' },
|
||||
{ title: '同比增长率(%)' },
|
||||
{ title: '环比增长率(%)' }
|
||||
])
|
||||
|
||||
// 定义 init 方法
|
||||
const init = () => {
|
||||
const columnObj = { label: '日期', prop: 'title' }
|
||||
columnsData.push(columnObj)
|
||||
|
||||
list.value.forEach((item, index) => {
|
||||
const columnObj = { label: item.time, prop: 'prop' + index }
|
||||
columnsData.push(columnObj)
|
||||
tableData[0]['prop' + index] = item.currentMonthCount
|
||||
tableData[1]['prop' + index] = item.lastMonthCount
|
||||
tableData[2]['prop' + index] = item.lastYearCount
|
||||
tableData[3]['prop' + index] =
|
||||
item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
|
||||
tableData[4]['prop' + index] =
|
||||
item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,227 @@
|
||||
<!-- 客户总量统计 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 TODO @scholar:统计列表的展示不对 -->
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="tableData">
|
||||
<el-table-column
|
||||
v-for="item in columnsData"
|
||||
:key="item.prop"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row[item.prop] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { EChartsOption } from 'echarts'
|
||||
import {
|
||||
StatisticsPerformanceApi,
|
||||
StatisticsPerformanceRespVO
|
||||
} from '@/api/crm/statistics/performance'
|
||||
|
||||
defineOptions({ name: 'ContractPricePerformance' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<StatisticsPerformanceRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 柱状图配置:纵向 */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true
|
||||
},
|
||||
legend: {},
|
||||
series: [
|
||||
{
|
||||
name: '当月合同金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '上月合同金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '去年同月合同金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '同比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '环比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataZoom: {
|
||||
xAxisIndex: false // 数据区域缩放:Y 轴不缩放
|
||||
},
|
||||
brush: {
|
||||
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
|
||||
},
|
||||
saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(元)',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '',
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}%'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name: '日期',
|
||||
data: []
|
||||
}
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const performanceList = await StatisticsPerformanceApi.getContractPricePerformance(
|
||||
props.queryParams
|
||||
)
|
||||
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.xAxis && echartsOption.xAxis['data']) {
|
||||
echartsOption.xAxis['data'] = performanceList.map((s: StatisticsPerformanceRespVO) => s.time)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.currentMonthCount
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
|
||||
echartsOption.series[1]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastMonthCount
|
||||
)
|
||||
echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) {
|
||||
echartsOption.series[2]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastYearCount
|
||||
)
|
||||
echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
|
||||
// 2.2 更新列表数据
|
||||
list.value = performanceList
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const columnsData = reactive([])
|
||||
const tableData = reactive([
|
||||
{ title: '当月合同金额统计(元)' },
|
||||
{ title: '上月合同金额统计(元)' },
|
||||
{ title: '去年当月合同金额统计(元)' },
|
||||
{ title: '同比增长率(%)' },
|
||||
{ title: '环比增长率(%)' }
|
||||
])
|
||||
|
||||
// 定义 init 方法
|
||||
const init = () => {
|
||||
const columnObj = { label: '日期', prop: 'title' }
|
||||
columnsData.push(columnObj)
|
||||
|
||||
list.value.forEach((item, index) => {
|
||||
const columnObj = { label: item.time, prop: 'prop' + index }
|
||||
columnsData.push(columnObj)
|
||||
tableData[0]['prop' + index] = item.currentMonthCount
|
||||
tableData[1]['prop' + index] = item.lastMonthCount
|
||||
tableData[2]['prop' + index] = item.lastYearCount
|
||||
tableData[3]['prop' + index] =
|
||||
item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
|
||||
tableData[4]['prop' + index] =
|
||||
item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,227 @@
|
||||
<!-- 客户总量统计 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 TODO @scholar:统计列表的展示不对 -->
|
||||
<el-card shadow="never" class="mt-16px">
|
||||
<el-table v-loading="loading" :data="tableData">
|
||||
<el-table-column
|
||||
v-for="item in columnsData"
|
||||
:key="item.prop"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row[item.prop] }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { EChartsOption } from 'echarts'
|
||||
import {
|
||||
StatisticsPerformanceApi,
|
||||
StatisticsPerformanceRespVO
|
||||
} from '@/api/crm/statistics/performance'
|
||||
|
||||
defineOptions({ name: 'ContractPricePerformance' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<StatisticsPerformanceRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 柱状图配置:纵向 */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true
|
||||
},
|
||||
legend: {},
|
||||
series: [
|
||||
{
|
||||
name: '当月回款金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '上月回款金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '去年同月回款金额(元)',
|
||||
type: 'line',
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '同比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
},
|
||||
{
|
||||
name: '环比增长率(%)',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: []
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataZoom: {
|
||||
xAxisIndex: false // 数据区域缩放:Y 轴不缩放
|
||||
},
|
||||
brush: {
|
||||
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
|
||||
},
|
||||
saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '金额(元)',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '',
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#BDBDBD',
|
||||
formatter: '{value}%'
|
||||
},
|
||||
/** 坐标轴轴线相关设置 */
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#BDBDBD'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e6e6e6'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
name: '日期',
|
||||
data: []
|
||||
}
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const performanceList = await StatisticsPerformanceApi.getReceivablePricePerformance(
|
||||
props.queryParams
|
||||
)
|
||||
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.xAxis && echartsOption.xAxis['data']) {
|
||||
echartsOption.xAxis['data'] = performanceList.map((s: StatisticsPerformanceRespVO) => s.time)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.currentMonthCount
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
|
||||
echartsOption.series[1]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastMonthCount
|
||||
)
|
||||
echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
if (echartsOption.series && echartsOption.series[2] && echartsOption.series[1]['data']) {
|
||||
echartsOption.series[2]['data'] = performanceList.map(
|
||||
(s: StatisticsPerformanceRespVO) => s.lastYearCount
|
||||
)
|
||||
echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
|
||||
s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
|
||||
)
|
||||
}
|
||||
|
||||
// 2.2 更新列表数据
|
||||
list.value = performanceList
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const columnsData = reactive([])
|
||||
const tableData = reactive([
|
||||
{ title: '当月回款金额统计(元)' },
|
||||
{ title: '上月回款金额统计(元)' },
|
||||
{ title: '去年当月回款金额统计(元)' },
|
||||
{ title: '同比增长率(%)' },
|
||||
{ title: '环比增长率(%)' }
|
||||
])
|
||||
|
||||
// 定义 init 方法
|
||||
const init = () => {
|
||||
const columnObj = { label: '日期', prop: 'title' }
|
||||
columnsData.push(columnObj)
|
||||
|
||||
list.value.forEach((item, index) => {
|
||||
const columnObj = { label: item.time, prop: 'prop' + index }
|
||||
columnsData.push(columnObj)
|
||||
tableData[0]['prop' + index] = item.currentMonthCount
|
||||
tableData[1]['prop' + index] = item.lastMonthCount
|
||||
tableData[2]['prop' + index] = item.lastYearCount
|
||||
tableData[3]['prop' + index] =
|
||||
item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
|
||||
tableData[4]['prop' + index] =
|
||||
item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await loadData()
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
147
src/views/crm/statistics/performance/index.vue
Normal file
147
src/views/crm/statistics/performance/index.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<!-- 数据统计 - 员工客户分析 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="时间范围" prop="orderDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.times"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.deptId"
|
||||
class="!w-240px"
|
||||
:data="deptList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
@change="queryParams.userId = undefined"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="userId">
|
||||
<el-select v-model="queryParams.userId" class="!w-240px" placeholder="员工" clearable>
|
||||
<el-option
|
||||
v-for="(user, index) in userListByDeptId"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
:key="index"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button>
|
||||
<el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 员工业绩统计 -->
|
||||
<el-col>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 员工合同统计 -->
|
||||
<el-tab-pane label="员工合同数量统计" name="ContractCountPerformance" lazy>
|
||||
<ContractCountPerformance :query-params="queryParams" ref="ContractCountPerformanceRef" />
|
||||
</el-tab-pane>
|
||||
<!-- 员工合同金额统计 -->
|
||||
<el-tab-pane label="员工合同金额统计" name="ContractPricePerformance" lazy>
|
||||
<ContractPricePerformance :query-params="queryParams" ref="ContractPricePerformanceRef" />
|
||||
</el-tab-pane>
|
||||
<!-- 员工回款金额统计 -->
|
||||
<el-tab-pane label="员工回款金额统计" name="followupType" lazy>
|
||||
<ReceivablePricePerformance
|
||||
:query-params="queryParams"
|
||||
ref="ReceivablePricePerformanceRef"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import ContractCountPerformance from './components/ContractCountPerformance.vue'
|
||||
import ContractPricePerformance from './components/ContractPricePerformance.vue'
|
||||
import ReceivablePricePerformance from './components/ReceivablePricePerformance.vue'
|
||||
|
||||
defineOptions({ name: 'CrmStatisticsPerformance' })
|
||||
|
||||
const queryParams = reactive({
|
||||
deptId: useUserStore().getUser.deptId,
|
||||
userId: undefined,
|
||||
times: [
|
||||
// 默认显示最近一周的数据
|
||||
formatDate(beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7))),
|
||||
formatDate(endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)))
|
||||
]
|
||||
})
|
||||
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const deptList = ref<Tree[]>([]) // 部门树形结构
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 全量用户清单
|
||||
// 根据选择的部门筛选员工清单
|
||||
const userListByDeptId = computed(() =>
|
||||
queryParams.deptId
|
||||
? userList.value.filter((u: UserApi.UserVO) => u.deptId === queryParams.deptId)
|
||||
: []
|
||||
)
|
||||
|
||||
// 活跃标签
|
||||
const activeTab = ref('ContractCountPerformance')
|
||||
// 1.员工合同数量统计
|
||||
const ContractCountPerformanceRef = ref()
|
||||
// 2.员工合同金额统计
|
||||
const ContractPricePerformanceRef = ref()
|
||||
// 3.员工回款金额统计
|
||||
const ReceivablePricePerformanceRef = ref()
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
switch (activeTab.value) {
|
||||
case 'ContractCountPerformance':
|
||||
ContractCountPerformanceRef.value?.loadData?.()
|
||||
break
|
||||
case 'ContractPricePerformance':
|
||||
ContractPricePerformanceRef.value?.loadData?.()
|
||||
break
|
||||
case 'ReceivablePricePerformance':
|
||||
ReceivablePricePerformanceRef.value?.loadData?.()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 当 activeTab 改变时,刷新当前活动的 tab
|
||||
watch(activeTab, () => {
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 加载部门树
|
||||
onMounted(async () => {
|
||||
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
userList.value = handleTree(await UserApi.getSimpleUserList())
|
||||
})
|
||||
</script>
|
||||
153
src/views/crm/statistics/portrait/components/CustomerAddress.vue
Normal file
153
src/views/crm/statistics/portrait/components/CustomerAddress.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<!-- 客户城市分布 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption2" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { EChartsOption } from 'echarts'
|
||||
import china from '@/assets/map/json/china.json'
|
||||
import echarts from '@/plugins/echarts'
|
||||
import {
|
||||
CrmStatisticCustomerAreaRespVO,
|
||||
StatisticsPortraitApi
|
||||
} from '@/api/crm/statistics/portrait'
|
||||
|
||||
// TODO @puhui999:address 换成 area 会更合适哈,
|
||||
defineOptions({ name: 'CustomerAddress' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
// 注册地图
|
||||
echarts?.registerMap('china', china as any)
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const areaStatisticsList = ref<CrmStatisticCustomerAreaRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 地图配置(全部客户) */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
showDelay: 0,
|
||||
transitionDuration: 0.2
|
||||
},
|
||||
visualMap: {
|
||||
text: ['高', '低'],
|
||||
realtime: false,
|
||||
calculable: true,
|
||||
top: 'middle',
|
||||
inRange: {
|
||||
color: ['#fff', '#3b82f6']
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '客户地域分布',
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
roam: false,
|
||||
selectedMode: false,
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 地图配置(成交客户) */
|
||||
const echartsOption2 = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
showDelay: 0,
|
||||
transitionDuration: 0.2
|
||||
},
|
||||
visualMap: {
|
||||
text: ['高', '低'],
|
||||
realtime: false,
|
||||
calculable: true,
|
||||
top: 'middle',
|
||||
inRange: {
|
||||
color: ['#fff', '#3b82f6']
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '客户地域分布',
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
roam: false,
|
||||
selectedMode: false,
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const areaList = await StatisticsPortraitApi.getCustomerArea(props.queryParams)
|
||||
areaStatisticsList.value = areaList.map((item: CrmStatisticCustomerAreaRespVO) => {
|
||||
return {
|
||||
...item,
|
||||
areaName: item.areaName // TODO @puhui999:这里最好注释下原因哈
|
||||
.replace('维吾尔自治区', '')
|
||||
.replace('壮族自治区', '')
|
||||
.replace('回族自治区', '')
|
||||
.replace('自治区', '')
|
||||
.replace('省', '')
|
||||
}
|
||||
})
|
||||
builderLeftMap()
|
||||
builderRightMap()
|
||||
loading.value = false
|
||||
}
|
||||
defineExpose({ loadData })
|
||||
|
||||
// TODO @puhui999:builder 改成 build 更合理哈
|
||||
const builderLeftMap = () => {
|
||||
let min = 0
|
||||
let max = 0
|
||||
echartsOption.series![0].data = areaStatisticsList.value.map((item) => {
|
||||
min = Math.min(min, item.customerCount || 0)
|
||||
max = Math.max(max, item.customerCount || 0)
|
||||
return { ...item, name: item.areaName, value: item.customerCount || 0 }
|
||||
})
|
||||
echartsOption.visualMap!['min'] = min
|
||||
echartsOption.visualMap!['max'] = max
|
||||
}
|
||||
|
||||
const builderRightMap = () => {
|
||||
let min = 0
|
||||
let max = 0
|
||||
echartsOption2.series![0].data = areaStatisticsList.value.map((item) => {
|
||||
min = Math.min(min, item.dealCount || 0)
|
||||
max = Math.max(max, item.dealCount || 0)
|
||||
return { ...item, name: item.areaName, value: item.dealCount || 0 }
|
||||
})
|
||||
echartsOption2.visualMap!['min'] = min
|
||||
echartsOption2.visualMap!['max'] = max
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,197 @@
|
||||
<!-- 客户行业分析 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption2" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 -->
|
||||
<el-card class="mt-16px" shadow="never">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="序号" type="index" width="80" />
|
||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
|
||||
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
|
||||
<el-table-column align="center" label="行业占比(%)" min-width="200" prop="industryPortion" />
|
||||
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
CrmStatisticCustomerIndustryRespVO,
|
||||
StatisticsPortraitApi
|
||||
} from '@/api/crm/statistics/portrait'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { getSumValue } from '@/utils'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'CustomerIndustry' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<CrmStatisticCustomerIndustryRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 饼图配置(全部客户) */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 饼图配置(成交客户) */
|
||||
const echartsOption2 = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const industryList = await StatisticsPortraitApi.getCustomerIndustry(props.queryParams)
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = industryList.map((r: CrmStatisticCustomerIndustryRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
|
||||
value: r.customerCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 2.2 更新 Echarts2 数据
|
||||
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
|
||||
echartsOption2.series[0]['data'] = industryList.map((r: CrmStatisticCustomerIndustryRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
|
||||
value: r.dealCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 3. 计算比例
|
||||
calculateProportion(industryList)
|
||||
list.value = industryList
|
||||
loading.value = false
|
||||
}
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 计算比例 */
|
||||
const calculateProportion = (sourceList: CrmStatisticCustomerIndustryRespVO[]) => {
|
||||
if (isEmpty(sourceList)) {
|
||||
return
|
||||
}
|
||||
// 这里类型丢失了所以重新搞个变量
|
||||
const list = sourceList as unknown as CrmStatisticCustomerIndustryRespVO[]
|
||||
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
|
||||
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
|
||||
list.forEach((item) => {
|
||||
item.industryPortion =
|
||||
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
|
||||
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
198
src/views/crm/statistics/portrait/components/CustomerLevel.vue
Normal file
198
src/views/crm/statistics/portrait/components/CustomerLevel.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<!-- 客户来源分析 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption2" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 -->
|
||||
<el-card class="mt-16px" shadow="never">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="序号" type="index" width="80" />
|
||||
<el-table-column align="center" label="客户级别" prop="level" width="200">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
|
||||
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
|
||||
<el-table-column align="center" label="级别占比(%)" min-width="200" prop="levelPortion" />
|
||||
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
CrmStatisticCustomerLevelRespVO,
|
||||
StatisticsPortraitApi
|
||||
} from '@/api/crm/statistics/portrait'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { getSumValue } from '@/utils'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
|
||||
defineOptions({ name: 'CustomerSource' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<CrmStatisticCustomerLevelRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 饼图配置(全部客户) */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 饼图配置(成交客户) */
|
||||
const echartsOption2 = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const levelList = await StatisticsPortraitApi.getCustomerLevel(props.queryParams)
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = levelList.map((r: CrmStatisticCustomerLevelRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.customerCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 2.2 更新 Echarts2 数据
|
||||
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
|
||||
echartsOption2.series[0]['data'] = levelList.map((r: CrmStatisticCustomerLevelRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.dealCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 3. 计算比例
|
||||
calculateProportion(levelList)
|
||||
list.value = levelList
|
||||
loading.value = false
|
||||
}
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 计算比例 */
|
||||
const calculateProportion = (levelList: CrmStatisticCustomerLevelRespVO[]) => {
|
||||
if (isEmpty(levelList)) {
|
||||
return
|
||||
}
|
||||
// 这里类型丢失了所以重新搞个变量
|
||||
const list = levelList as unknown as CrmStatisticCustomerLevelRespVO[]
|
||||
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
|
||||
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
|
||||
list.forEach((item) => {
|
||||
// TODO @puhui999:可以使用 erpCalculatePercentage 方法
|
||||
item.levelPortion =
|
||||
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
|
||||
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
197
src/views/crm/statistics/portrait/components/CustomerSource.vue
Normal file
197
src/views/crm/statistics/portrait/components/CustomerSource.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<!-- 客户来源分析 -->
|
||||
<template>
|
||||
<!-- Echarts图 -->
|
||||
<el-card shadow="never">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="500" :options="echartsOption2" />
|
||||
</el-skeleton>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计列表 -->
|
||||
<el-card class="mt-16px" shadow="never">
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column align="center" label="序号" type="index" width="80" />
|
||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="客户个数" min-width="200" prop="customerCount" />
|
||||
<el-table-column align="center" label="成交个数" min-width="200" prop="dealCount" />
|
||||
<el-table-column align="center" label="来源占比(%)" min-width="200" prop="sourcePortion" />
|
||||
<el-table-column align="center" label="成交占比(%)" min-width="200" prop="dealPortion" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
CrmStatisticCustomerSourceRespVO,
|
||||
StatisticsPortraitApi
|
||||
} from '@/api/crm/statistics/portrait'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { getSumValue } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'CustomerSource' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const list = ref<CrmStatisticCustomerSourceRespVO[]>([]) // 列表的数据
|
||||
|
||||
/** 饼图配置(全部客户) */
|
||||
const echartsOption = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 饼图配置(成交客户) */
|
||||
const echartsOption2 = reactive<EChartsOption>({
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left'
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' } // 保存为图片
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}) as EChartsOption
|
||||
|
||||
/** 获取统计数据 */
|
||||
const loadData = async () => {
|
||||
// 1. 加载统计数据
|
||||
loading.value = true
|
||||
const sourceList = await StatisticsPortraitApi.getCustomerSource(props.queryParams)
|
||||
// 2.1 更新 Echarts 数据
|
||||
if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
|
||||
echartsOption.series[0]['data'] = sourceList.map((r: CrmStatisticCustomerSourceRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.customerCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 2.2 更新 Echarts2 数据
|
||||
if (echartsOption2.series && echartsOption2.series[0] && echartsOption2.series[0]['data']) {
|
||||
echartsOption2.series[0]['data'] = sourceList.map((r: CrmStatisticCustomerSourceRespVO) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.dealCount
|
||||
}
|
||||
})
|
||||
}
|
||||
// 3. 计算比例
|
||||
calculateProportion(sourceList)
|
||||
list.value = sourceList
|
||||
loading.value = false
|
||||
}
|
||||
defineExpose({ loadData })
|
||||
|
||||
/** 计算比例 */
|
||||
const calculateProportion = (sourceList: CrmStatisticCustomerSourceRespVO[]) => {
|
||||
if (isEmpty(sourceList)) {
|
||||
return
|
||||
}
|
||||
// 这里类型丢失了所以重新搞个变量
|
||||
const list = sourceList as unknown as CrmStatisticCustomerSourceRespVO[]
|
||||
const sumCustomerCount = getSumValue(list.map((item) => item.customerCount))
|
||||
const sumDealCount = getSumValue(list.map((item) => item.dealCount))
|
||||
list.forEach((item) => {
|
||||
item.sourcePortion =
|
||||
item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2)
|
||||
item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2)
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
157
src/views/crm/statistics/portrait/index.vue
Normal file
157
src/views/crm/statistics/portrait/index.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<!-- 数据统计 - 客户画像 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="时间范围" prop="orderDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.times"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.deptId"
|
||||
:data="deptList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="!w-240px"
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
@change="queryParams.userId = undefined"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="userId">
|
||||
<el-select v-model="queryParams.userId" class="!w-240px" clearable placeholder="员工">
|
||||
<el-option
|
||||
v-for="(user, index) in userListByDeptId"
|
||||
:key="index"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 客户统计 -->
|
||||
<el-col>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 城市分布分析 -->
|
||||
<el-tab-pane label="城市分布分析" lazy name="addressRef">
|
||||
<CustomerAddress ref="addressRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户级别分析 -->
|
||||
<el-tab-pane label="客户级别分析" lazy name="levelRef">
|
||||
<CustomerLevel ref="levelRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户来源分析 -->
|
||||
<el-tab-pane label="客户来源分析" lazy name="sourceRef">
|
||||
<CustomerSource ref="sourceRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
<!-- 客户行业分析 -->
|
||||
<el-tab-pane label="客户行业分析" lazy name="industryRef">
|
||||
<CustomerIndustry ref="industryRef" :query-params="queryParams" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
// TODO @puhui999:最好命名带上模块名,如:CrmStatisticsPortrait
|
||||
import CustomerAddress from './components/CustomerAddress.vue'
|
||||
import CustomerIndustry from './components/CustomerIndustry.vue'
|
||||
import CustomerSource from './components/CustomerSource.vue'
|
||||
import CustomerLevel from './components/CustomerLevel.vue'
|
||||
|
||||
defineOptions({ name: 'CrmStatisticsPortrait' })
|
||||
|
||||
const queryParams = reactive({
|
||||
deptId: useUserStore().getUser.deptId,
|
||||
userId: undefined,
|
||||
times: [
|
||||
// 默认显示最近一周的数据
|
||||
formatDate(beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7))),
|
||||
formatDate(endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)))
|
||||
]
|
||||
})
|
||||
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const deptList = ref<Tree[]>([]) // 部门树形结构
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 全量用户清单
|
||||
|
||||
/** 根据选择的部门筛选员工清单 */
|
||||
const userListByDeptId = computed(() =>
|
||||
queryParams.deptId
|
||||
? userList.value.filter((u: UserApi.UserVO) => u.deptId === queryParams.deptId)
|
||||
: []
|
||||
)
|
||||
|
||||
const activeTab = ref('addressRef') // 活跃标签
|
||||
const addressRef = ref() // 客户地区分布
|
||||
const levelRef = ref() // 客户级别
|
||||
const sourceRef = ref() // 客户来源
|
||||
const industryRef = ref() // 客户行业
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
switch (activeTab.value) {
|
||||
case 'addressRef':
|
||||
addressRef.value?.loadData?.()
|
||||
break
|
||||
case 'levelRef':
|
||||
levelRef.value?.loadData?.()
|
||||
break
|
||||
case 'sourceRef':
|
||||
sourceRef.value?.loadData?.()
|
||||
break
|
||||
case 'industryRef':
|
||||
industryRef.value?.loadData?.()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 当 activeTab 改变时,刷新当前活动的 tab */
|
||||
watch(activeTab, () => {
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
userList.value = handleTree(await UserApi.getSimpleUserList())
|
||||
})
|
||||
</script>
|
||||
@@ -22,7 +22,7 @@ import { StatisticsRankApi, StatisticsRankRespVO } from '@/api/crm/statistics/ra
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { clone } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'ContactsCountRank' })
|
||||
defineOptions({ name: 'ContactCountRank' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
@@ -13,7 +13,13 @@
|
||||
<el-table-column label="公司排名" align="center" type="index" width="80" />
|
||||
<el-table-column label="签订人" align="center" prop="nickname" min-width="200" />
|
||||
<el-table-column label="部门" align="center" prop="deptName" min-width="200" />
|
||||
<el-table-column label="合同金额(元)" align="center" prop="count" min-width="200" />
|
||||
<el-table-column
|
||||
label="合同金额(元)"
|
||||
align="center"
|
||||
prop="count"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -21,6 +27,7 @@
|
||||
import { StatisticsRankApi, StatisticsRankRespVO } from '@/api/crm/statistics/rank'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { clone } from 'lodash-es'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'ContractPriceRank' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
@@ -13,7 +13,13 @@
|
||||
<el-table-column label="公司排名" align="center" type="index" width="80" />
|
||||
<el-table-column label="签订人" align="center" prop="nickname" min-width="200" />
|
||||
<el-table-column label="部门" align="center" prop="deptName" min-width="200" />
|
||||
<el-table-column label="回款金额(元)" align="center" prop="count" min-width="200" />
|
||||
<el-table-column
|
||||
label="回款金额(元)"
|
||||
align="center"
|
||||
prop="count"
|
||||
min-width="200"
|
||||
:formatter="erpPriceTableColumnFormatter"
|
||||
/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -21,6 +27,7 @@
|
||||
import { StatisticsRankApi, StatisticsRankRespVO } from '@/api/crm/statistics/rank'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { clone } from 'lodash-es'
|
||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'ReceivablePriceRank' })
|
||||
const props = defineProps<{ queryParams: any }>() // 搜索参数
|
||||
@@ -29,6 +29,7 @@
|
||||
check-strictly
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -62,8 +63,8 @@
|
||||
<CustomerCountRank :query-params="queryParams" ref="customerCountRankRef" />
|
||||
</el-tab-pane>
|
||||
<!-- 新增联系人数排行 -->
|
||||
<el-tab-pane label="新增联系人数排行" name="contactsCountRank" lazy>
|
||||
<ContactsCountRank :query-params="queryParams" ref="contactsCountRankRef" />
|
||||
<el-tab-pane label="新增联系人数排行" name="contactCountRank" lazy>
|
||||
<ContactCountRank :query-params="queryParams" ref="contactCountRankRef" />
|
||||
</el-tab-pane>
|
||||
<!-- 跟进次数排行 -->
|
||||
<el-tab-pane label="跟进次数排行" name="followCountRank" lazy>
|
||||
@@ -77,14 +78,14 @@
|
||||
</el-col>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ContractPriceRank from './ContractPriceRank.vue'
|
||||
import ReceivablePriceRank from './ReceivablePriceRank.vue'
|
||||
import ContractCountRank from './ContractCountRank.vue'
|
||||
import ProductSalesRank from './ProductSalesRank.vue'
|
||||
import CustomerCountRank from './CustomerCountRank.vue'
|
||||
import ContactsCountRank from './ContactsCountRank.vue'
|
||||
import FollowCountRank from './FollowCountRank.vue'
|
||||
import FollowCustomerCountRank from './FollowCustomerCountRank.vue'
|
||||
import ContractPriceRank from './components/ContractPriceRank.vue'
|
||||
import ReceivablePriceRank from './components/ReceivablePriceRank.vue'
|
||||
import ContractCountRank from './components/ContractCountRank.vue'
|
||||
import ProductSalesRank from './components/ProductSalesRank.vue'
|
||||
import CustomerCountRank from './components/CustomerCountRank.vue'
|
||||
import ContactCountRank from './components/ContactCountRank.vue'
|
||||
import FollowCountRank from './components/FollowCountRank.vue'
|
||||
import FollowCustomerCountRank from './components/FollowCustomerCountRank.vue'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
|
||||
@@ -109,35 +110,35 @@ const receivablePriceRankRef = ref() // ReceivablePriceRank 组件的引用
|
||||
const contractCountRankRef = ref() // ContractCountRank 组件的引用
|
||||
const productSalesRankRef = ref() // ProductSalesRank 组件的引用
|
||||
const customerCountRankRef = ref() // CustomerCountRank 组件的引用
|
||||
const contactsCountRankRef = ref() // ContactsCountRank 组件的引用
|
||||
const contactCountRankRef = ref() // ContactCountRank 组件的引用
|
||||
const followCountRankRef = ref() // FollowCountRank 组件的引用
|
||||
const followCustomerCountRankRef = ref() // FollowCustomerCountRank 组件的引用
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
switch (activeTab.value) {
|
||||
case 'contractPriceRank':
|
||||
case 'contractPriceRank': // 合同金额排行
|
||||
contractPriceRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'receivablePriceRank':
|
||||
case 'receivablePriceRank': // 回款金额排行
|
||||
receivablePriceRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'contractCountRank':
|
||||
case 'contractCountRank': // 签约合同排行
|
||||
contractCountRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'productSalesRank':
|
||||
case 'productSalesRank': // 产品销量排行
|
||||
productSalesRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'customerCountRank':
|
||||
case 'customerCountRank': // 新增客户数排行
|
||||
customerCountRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'contactsCountRank':
|
||||
contactsCountRankRef.value?.loadData?.()
|
||||
case 'contactCountRank': // 新增联系人数排行
|
||||
contactCountRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'followCountRank':
|
||||
case 'followCountRank': // 跟进次数排行
|
||||
followCountRankRef.value?.loadData?.()
|
||||
break
|
||||
case 'followCustomerCountRank':
|
||||
case 'followCustomerCountRank': // 跟进客户数排行
|
||||
followCustomerCountRankRef.value?.loadData?.()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
<el-descriptions-item label="请求参数">
|
||||
{{ detailData.requestParams }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求结果">
|
||||
{{ detailData.responseBody }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求时间">
|
||||
{{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }}
|
||||
</el-descriptions-item>
|
||||
@@ -36,6 +39,15 @@
|
||||
失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作模块">
|
||||
{{ detailData.operateModule }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作名">
|
||||
{{ detailData.operateName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作名">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="detailData.operateType" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -91,16 +91,16 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="日志编号" align="center" prop="id" />
|
||||
<el-table-column label="日志编号" align="center" prop="id" width="100" fix="right" />
|
||||
<el-table-column label="用户编号" align="center" prop="userId" />
|
||||
<el-table-column label="用户类型" align="center" prop="userType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应用名" align="center" prop="applicationName" />
|
||||
<el-table-column label="应用名" align="center" prop="applicationName" width="150" />
|
||||
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
|
||||
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
|
||||
<el-table-column label="请求地址" align="center" prop="requestUrl" width="500" />
|
||||
<el-table-column label="请求时间" align="center" prop="beginTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.beginTime) }}</span>
|
||||
@@ -114,7 +114,14 @@
|
||||
{{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<el-table-column label="操作模块" align="center" prop="operateModule" width="180" />
|
||||
<el-table-column label="操作名" align="center" prop="operateName" width="180" />
|
||||
<el-table-column label="操作类型" align="center" prop="operateType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="scope.row.operateType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" width="60">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
|
||||
@@ -23,15 +23,14 @@
|
||||
</el-button>
|
||||
<el-scrollbar height="580">
|
||||
<div>
|
||||
<pre><code class="hljs" v-dompurify-html="highlightedCode(formData)"></code></pre>
|
||||
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'InfraBuild' })
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import { useFormCreateDesigner } from '@/components/FormCreate'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { isString } from '@/utils/is'
|
||||
|
||||
@@ -41,6 +40,8 @@ import xml from 'highlight.js/lib/languages/java'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import formCreate from '@form-create/element-ui'
|
||||
|
||||
defineOptions({ name: 'InfraBuild' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息
|
||||
|
||||
@@ -49,6 +50,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formType = ref(-1) // 表单的类型:0 - 生成 JSON;1 - 生成 Options;2 - 生成组件
|
||||
const formData = ref('') // 表单数据
|
||||
useFormCreateDesigner(designer) // 表单设计器增强
|
||||
|
||||
/** 打开弹窗 */
|
||||
const openModel = (title: string) => {
|
||||
@@ -82,14 +84,13 @@ const makeTemplate = () => {
|
||||
const opt = designer.value.getOption()
|
||||
return `<template>
|
||||
<form-create
|
||||
v-model="fapi"
|
||||
v-model:api="fApi"
|
||||
:rule="rule"
|
||||
:option="option"
|
||||
@submit="onSubmit"
|
||||
></form-create>
|
||||
</template>
|
||||
<script setup lang=ts>
|
||||
import formCreate from "@form-create/element-ui";
|
||||
const faps = ref(null)
|
||||
const rule = ref('')
|
||||
const option = ref('')
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="180px" label="公众号 APPID" prop="config.appId">
|
||||
<el-form-item label-width="180px" label="微信 APPID" prop="config.appId">
|
||||
<el-input
|
||||
v-model="formData.config.appId"
|
||||
placeholder="请输入公众号 APPID"
|
||||
placeholder="请输入微信 APPID"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'UReportData' })
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/ureport/designer?token=' + getAccessToken())
|
||||
</script>
|
||||
@@ -4,14 +4,14 @@
|
||||
<el-descriptions-item label="日志主键" min-width="120">
|
||||
{{ detailData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="链路追踪">
|
||||
<el-descriptions-item label="链路追踪" v-if="detailData.traceId">
|
||||
{{ detailData.traceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人编号">
|
||||
{{ detailData.userId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人名字">
|
||||
{{ detailData.userNickname }}
|
||||
{{ detailData.userName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人 IP">
|
||||
{{ detailData.userIp }}
|
||||
@@ -20,39 +20,25 @@
|
||||
{{ detailData.userAgent }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作模块">
|
||||
{{ detailData.module }}
|
||||
{{ detailData.type }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作名">
|
||||
{{ detailData.name }}
|
||||
{{ detailData.subType }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.content" label="操作内容">
|
||||
{{ detailData.content }}
|
||||
<el-descriptions-item label="操作内容">
|
||||
{{ detailData.action }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.exts" label="操作拓展参数">
|
||||
{{ detailData.exts }}
|
||||
<el-descriptions-item v-if="detailData.extra" label="操作拓展参数">
|
||||
{{ detailData.extra }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="请求 URL">
|
||||
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Java 方法名">
|
||||
{{ detailData.javaMethod }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Java 方法参数">
|
||||
{{ detailData.javaMethodArgs }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">
|
||||
{{ formatDate(detailData.startTime) }}
|
||||
{{ formatDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行时长">{{ detailData.duration }} ms</el-descriptions-item>
|
||||
<el-descriptions-item label="操作结果">
|
||||
<div v-if="detailData.resultCode === 0">正常</div>
|
||||
<div v-else>失败({{ detailData.resultCode }})</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.resultCode === 0" label="操作结果">
|
||||
{{ detailData.resultData }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.resultCode > 0" label="失败提示">
|
||||
{{ detailData.resultMsg }}
|
||||
<el-descriptions-item label="业务编号">
|
||||
{{ detailData.bizId }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
|
||||
@@ -10,58 +10,65 @@
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="系统模块" prop="module">
|
||||
<el-input
|
||||
v-model="queryParams.module"
|
||||
placeholder="请输入系统模块"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人员" prop="userNickname">
|
||||
<el-input
|
||||
v-model="queryParams.userNickname"
|
||||
placeholder="请输入操作人员"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型" prop="type">
|
||||
<el-form-item label="操作人" prop="userId">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择操作类型"
|
||||
clearable
|
||||
v-model="queryParams.userId"
|
||||
multiple
|
||||
placeholder="请输入操作人员"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作状态" prop="success">
|
||||
<el-select
|
||||
v-model="queryParams.success"
|
||||
placeholder="请选择操作状态"
|
||||
<el-form-item label="操作模块" prop="type">
|
||||
<el-input
|
||||
v-model="queryParams.type"
|
||||
placeholder="请输入操作模块"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option key="true" label="成功" :value="true" />
|
||||
<el-option key="false" label="失败" :value="false" />
|
||||
</el-select>
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间" prop="startTime">
|
||||
<el-form-item label="操作模块" prop="subType">
|
||||
<el-input
|
||||
v-model="queryParams.subType"
|
||||
placeholder="请输入操作模块"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作内容" prop="action">
|
||||
<el-input
|
||||
v-model="queryParams.action"
|
||||
placeholder="请输入操作名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.startTime"
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务编号" prop="bizId">
|
||||
<el-input
|
||||
v-model="queryParams.bizId"
|
||||
placeholder="请输入业务编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -84,33 +91,21 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="日志编号" align="center" prop="id" />
|
||||
<el-table-column label="操作模块" align="center" prop="module" width="180" />
|
||||
<el-table-column label="操作名" align="center" prop="name" width="180" />
|
||||
<el-table-column label="操作类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作人" align="center" prop="userNickname" />
|
||||
<el-table-column label="操作结果" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="日志编号" align="center" prop="id" width="100" />
|
||||
<el-table-column label="操作人" align="center" prop="userName" width="120" />
|
||||
<el-table-column label="操作模块" align="center" prop="type" width="120" />
|
||||
<el-table-column label="操作名" align="center" prop="subType" width="160" />
|
||||
<el-table-column label="操作内容" align="center" prop="action" />
|
||||
<el-table-column
|
||||
label="操作时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="执行时长" align="center" prop="startTime">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.duration }} ms</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<el-table-column label="业务编号" align="center" prop="bizId" width="120" />
|
||||
<el-table-column label="IP" align="center" prop="userIp" width="120" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="60">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
@@ -136,11 +131,12 @@
|
||||
<OperateLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as OperateLogApi from '@/api/system/operatelog'
|
||||
import OperateLogDetail from './OperateLogDetail.vue'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
defineOptions({ name: 'SystemOperateLog' })
|
||||
|
||||
@@ -152,11 +148,12 @@ const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
module: undefined,
|
||||
userNickname: undefined,
|
||||
userId: undefined,
|
||||
type: undefined,
|
||||
success: undefined,
|
||||
startTime: []
|
||||
subType: undefined,
|
||||
action: undefined,
|
||||
createTime: [],
|
||||
bizId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
@@ -207,7 +204,9 @@ const handleExport = async () => {
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 获得用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['system:notice:create']"
|
||||
v-hasPermi="['system:post:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
@@ -50,7 +50,7 @@
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['infra:config:export']"
|
||||
v-hasPermi="['infra:post:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
|
||||
@@ -103,6 +103,10 @@ const open = async (row: RoleApi.RoleVO) => {
|
||||
formData.code = row.code
|
||||
formData.dataScope = row.dataScope
|
||||
await nextTick()
|
||||
row.dataScopeDeptIds?.forEach((deptId: number): void => {
|
||||
|
||||
await nextTick()
|
||||
// 需要在 DOM 渲染完成后,再设置选中状态
|
||||
row.dataScopeDeptIds?.forEach((deptId: number): void => {
|
||||
treeRef.value.setChecked(deptId, true, false)
|
||||
})
|
||||
|
||||
@@ -59,11 +59,11 @@ const formData = ref({
|
||||
remark: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],
|
||||
sort: [{ required: true, message: '岗位顺序不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],
|
||||
remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]
|
||||
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '角色标识不能为空', trigger: 'change' }],
|
||||
sort: [{ required: true, message: '显示顺序不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
remark: [{ required: false, message: '备注不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
||||
28
src/views/system/user/components/UserSelect.vue
Normal file
28
src/views/system/user/components/UserSelect.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<!-- TODO puhui999: 先单独一个后面封装成通用选择组件 -->
|
||||
<template>
|
||||
<el-select class="w-1/1" v-bind="attrs">
|
||||
<el-option
|
||||
v-for="(dict, index) in userOptions"
|
||||
:key="index"
|
||||
:label="dict.nickname"
|
||||
:value="dict.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserSelect' })
|
||||
|
||||
const attrs = useAttrs()
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户下拉数据
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await UserApi.getSimpleUserList()
|
||||
if (!data || data.length === 0) {
|
||||
return
|
||||
}
|
||||
userOptions.value = data
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user