Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

This commit is contained in:
YunaiV
2025-02-13 19:01:02 +08:00
54 changed files with 7554 additions and 6099 deletions

View File

@@ -88,6 +88,9 @@
/>
</el-tooltip>
<el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
<div v-else class="flow-icon">
<span style="font-size: 12px; color: #fff">{{ sliceName(row.name,0,2) }}</span>
</div>
{{ row.name }}
</div>
</template>
@@ -249,6 +252,11 @@
</div>
</template>
</Dialog>
<!-- 弹窗:表单详情 -->
<Dialog title="表单详情" :fullscreen="true" v-model="formDetailVisible">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
@@ -265,6 +273,7 @@ import { useAppStore } from '@/store/modules/app'
import { cloneDeep, isEqual } from 'lodash-es'
import { useTagsView } from '@/hooks/web/useTagsView'
import { useDebounceFn } from '@vueuse/core'
import { sliceName } from '@/utils/index'
defineOptions({ name: 'BpmModel' })
@@ -437,11 +446,10 @@ const handleChangeState = async (row: any) => {
/** 发布流程 */
const handleDeploy = async (row: any) => {
try {
// 删除的二次确认
await message.confirm('是否部署该流程!!')
await message.confirm('是否确认发布该流程?')
// 发起部署
await ModelApi.deployModel(row.id)
message.success(t('部署成功'))
message.success(t('发布成功'))
// 刷新列表
emit('success')
} catch {}
@@ -464,7 +472,7 @@ const formDetailPreview = ref({
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == 10) {
if (row.formType == BpmModelFormType.NORMAL) {
// 设置表单
const data = await FormApi.getForm(row.formId)
setConfAndFields2(formDetailPreview, data.conf, data.fields)
@@ -617,6 +625,17 @@ watchEffect(() => {
}
</style>
<style lang="scss" scoped>
.flow-icon {
display: flex;
width: 38px;
height: 38px;
margin-right: 10px;
background-color: var(--el-color-primary);
border-radius: 0.25rem;
align-items: center;
justify-content: center;
}
.category-draggable-model {
:deep(.el-table__cell) {
overflow: hidden;

View File

@@ -6,7 +6,7 @@
class="!w-440px"
v-model="modelData.key"
:disabled="!!modelData.id"
placeholder="请输入流标识"
placeholder="请输入流标识,以字母或下划线开头"
/>
<el-tooltip
class="item"
@@ -41,7 +41,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="流程图标" prop="icon" class="mb-20px">
<el-form-item label="流程图标" class="mb-20px">
<UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
</el-form-item>
<el-form-item label="流程描述" prop="description" class="mb-20px">
@@ -155,7 +155,6 @@ const rules = {
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]

View File

@@ -285,9 +285,8 @@ const handleSave = async () => {
} else {
// 新增场景
formData.value.id = await ModelApi.createModel(modelData)
message.success('新增成功')
try {
await message.confirm('创建流程成功,是否继续编辑?')
await message.confirm('流程创建成功,是否继续编辑?')
// 用户点击继续编辑,跳转到编辑页面
await nextTick()
// 先删除当前页签
@@ -317,7 +316,6 @@ const handleDeploy = async () => {
if (!formData.value.id) {
await message.confirm('是否确认发布该流程?')
}
// 校验所有步骤
await validateAllSteps()

View File

@@ -58,7 +58,16 @@
>
<template #default>
<div class="flex">
<el-image :src="definition.icon" class="w-32px h-32px" />
<el-image
v-if="definition.icon"
:src="definition.icon"
class="w-32px h-32px"
/>
<div v-else class="flow-icon">
<span style="font-size: 12px; color: #fff">{{
sliceName(definition.name,0,2)
}}</span>
</div>
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div>
</template>
@@ -88,6 +97,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
import { groupBy } from 'lodash-es'
import { sliceName } from '@/utils/index'
defineOptions({ name: 'BpmProcessInstanceCreate' })
@@ -282,13 +292,25 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.flow-icon {
display: flex;
width: 32px;
height: 32px;
margin-right: 10px;
background-color: var(--el-color-primary);
border-radius: 0.25rem;
align-items: center;
justify-content: center;
}
.process-definition-container::before {
content: '';
border-left: 1px solid #e6e6e6;
position: absolute;
left: 20.8%;
height: 100%;
border-left: 1px solid #e6e6e6;
content: '';
}
:deep() {
.definition-item-card {
.el-card__body {

View File

@@ -66,7 +66,11 @@
</el-table-column>
<el-table-column label="产品单价" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
<el-form-item
:prop="`${$index}.productPrice`"
:rules="formRules.productPrice"
class="mb-0px!"
>
<el-input-number
v-model="row.productPrice"
controls-position="right"
@@ -153,6 +157,7 @@ const formLoading = ref(false) // 表单的加载中
const formData = ref([])
const formRules = reactive({
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
productPrice: [{ required: true, message: '产品单价不能为空', trigger: 'blur' }],
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
})
const formRef = ref([]) // 表单 Ref

View File

@@ -7,7 +7,7 @@
<el-descriptions-item label="任务名称">
{{ detailData.name }}
</el-descriptions-item>
<el-descriptions-item label="任务名称">
<el-descriptions-item label="任务状态">
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="detailData.status" />
</el-descriptions-item>
<el-descriptions-item label="处理器的名字">

View File

@@ -52,7 +52,9 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const formData = ref<DiyTemplateApi.DiyTemplatePropertyVO>()
const formRef = ref() // 表单 Ref
// 当前编辑的属性
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>()
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>({
property: ''
} as DiyPageApi.DiyPageVO)
// templateItem 对应的缓存
const currentFormDataMap = ref<
Map<string, DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>
@@ -92,17 +94,21 @@ const handleTemplateItemChange = (val: number) => {
// 编辑模板
if (val === 0) {
libs.value = templateLibs
currentFormData.value = isEmpty(data) ? formData.value : data
currentFormData.value = (isEmpty(data) ? formData.value : data) as
| DiyTemplateApi.DiyTemplatePropertyVO
| DiyPageApi.DiyPageVO
return
}
// 编辑页面
libs.value = PAGE_LIBS
currentFormData.value = isEmpty(data)
? formData.value!.pages.find(
(page: DiyPageApi.DiyPageVO) => page.name === templateItems[val].name
)
: data
currentFormData.value = (
isEmpty(data)
? formData.value!.pages.find(
(page: DiyPageApi.DiyPageVO) => page.name === templateItems[val].name
)
: data
) as DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO
}
// 提交表单
@@ -170,7 +176,9 @@ const recoverPageIndex = () => {
sessionStorage.removeItem(DIY_PAGE_INDEX_KEY)
// 重新初始化数据
currentFormData.value = formData.value
currentFormData.value = formData.value as
| DiyTemplateApi.DiyTemplatePropertyVO
| DiyPageApi.DiyPageVO
currentFormDataMap.value = new Map<
string,
DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO

View File

@@ -279,8 +279,9 @@ const handleSendMessage = async (event: any) => {
return
}
// 1. 校验消息是否为空
if (isEmpty(unref(message.value))) {
if (isEmpty(unref(message.value)?.trim())) {
messageTool.notifyWarning('请输入消息后再发送哦!')
message.value = ''
return
}
// 2. 组织发送消息

View File

@@ -37,40 +37,45 @@ const { data, close, open } = useWebSocket(server.value, {
})
/** 监听 WebSocket 数据 */
watchEffect(() => {
if (!data.value) {
return
watch(
() => data.value,
(newData) => {
if (!newData) return
try {
// 1. 收到心跳
if (newData === 'pong') return
// 2.1 解析 type 消息类型
const jsonMessage = JSON.parse(newData)
const type = jsonMessage.type
if (!type) {
message.error('未知的消息类型:' + newData)
return
}
// 2.2 消息类型KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
const message = JSON.parse(jsonMessage.content)
// 刷新会话列表
kefuStore.updateConversation(message.conversationId)
// 刷新消息列表
keFuChatBoxRef.value?.refreshMessageList(message)
return
}
// 2.3 消息类型KEFU_MESSAGE_ADMIN_READ
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
// 更新会话已读
kefuStore.updateConversationStatus(jsonParse(jsonMessage.content))
}
} catch (error) {
console.error(error)
}
},
{
immediate: false // 不立即执行
}
try {
// 1. 收到心跳
if (data.value === 'pong') {
return
}
// 2.1 解析 type 消息类型
const jsonMessage = JSON.parse(data.value)
const type = jsonMessage.type
if (!type) {
message.error('未知的消息类型:' + data.value)
return
}
// 2.2 消息类型KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
const message = JSON.parse(jsonMessage.content)
// 刷新会话列表
kefuStore.updateConversation(message.conversationId)
// 刷新消息列表
keFuChatBoxRef.value?.refreshMessageList(message)
return
}
// 2.3 消息类型KEFU_MESSAGE_ADMIN_READ
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
// 更新会话已读
kefuStore.updateConversationStatus(jsonParse(jsonMessage.content))
}
} catch (error) {
console.error(error)
}
})
)
// ======================= WebSocket end =======================
/** 加载指定会话的消息列表 */

View File

@@ -26,9 +26,23 @@
placeholder="请输入微信 APPID"
/>
</el-form-item>
<el-form-item label-width="180px">
<a
href="https://pay.weixin.qq.com/index.php/extend/merchant_appid/mapay_platform/account_manage"
target="_blank"
>
前往微信商户平台查看 APPID
</a>
</el-form-item>
<el-form-item label="商户号" label-width="180px" prop="config.mchId">
<el-input v-model="formData.config.mchId" :style="{ width: '100%' }" />
</el-form-item>
<el-form-item label-width="180px">
<a href="https://pay.weixin.qq.com/index.php/extend/pay_setting" target="_blank">
前往微信商户平台查看商户号
</a>
</el-form-item>
<el-form-item label="渠道状态" label-width="180px" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
@@ -123,6 +137,14 @@
placeholder="请输入证书序列号"
/>
</el-form-item>
<el-form-item label-width="180px">
<a
href="https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage"
target="_blank"
>
前往微信商户平台查看证书序列号
</a>
</el-form-item>
</div>
<el-form-item label="备注" label-width="180px" prop="remark">
<el-input v-model="formData.remark" :style="{ width: '100%' }" />

View File

@@ -53,6 +53,10 @@
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
<el-button plain type="danger" @click="toggleExpandAll">
<Icon class="mr-5px" icon="ep:sort" />
展开/折叠
</el-button>
<el-button plain @click="refreshMenu">
<Icon class="mr-5px" icon="ep:refresh" />
刷新菜单缓存
@@ -63,149 +67,169 @@
<!-- 列表 -->
<ContentWrap>
<div style="height: 700px">
<!-- AutoResizer 自动调节大小 -->
<el-auto-resizer>
<template #default="{ height, width }">
<!-- Virtualized Table 虚拟化表格高性能解决表格在大数据量下的卡顿问题 -->
<el-table-v2
v-loading="loading"
:columns="columns"
:data="list"
:width="width"
:height="height"
expand-column-key="name"
/>
</template>
</el-auto-resizer>
</div>
<el-auto-resizer>
<template #default="{ width }">
<el-table-v2
v-model:expanded-row-keys="expandedRowKeys"
:columns="columns"
:data="list"
:expand-column-key="columns[0].key"
:height="1000"
:width="width"
fixed
row-key="id"
/>
</template>
</el-auto-resizer>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<MenuForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
<script lang="tsx" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree } from '@/utils/tree'
import * as MenuApi from '@/api/system/menu'
import { MenuVO } from '@/api/system/menu'
import MenuForm from './MenuForm.vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { h } from 'vue'
import { Column, ElButton } from 'element-plus'
import DictTag from '@/components/DictTag/src/DictTag.vue'
import { Icon } from '@/components/Icon'
import { hasPermission } from '@/directives/permission/hasPermi'
import { ElButton, TableV2FixedDir } from 'element-plus'
import { checkPermi } from '@/utils/permission'
import { CommonStatusEnum } from '@/utils/constants'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
defineOptions({ name: 'SystemMenu' })
// 虚拟列表表格
const columns = [
{
key: 'name',
title: '菜单名称',
dataKey: 'name',
width: 250,
fixed: TableV2FixedDir.LEFT
},
{
key: 'icon',
title: '图标',
dataKey: 'icon',
width: 100,
align: 'center',
cellRenderer: ({ cellData: icon }) => <Icon icon={icon} />
},
{
key: 'sort',
title: '排序',
dataKey: 'sort',
width: 60
},
{
key: 'permission',
title: '权限标识',
dataKey: 'permission',
width: 300
},
{
key: 'component',
title: '组件路径',
dataKey: 'component',
width: 500
},
{
key: 'componentName',
title: '组件名称',
dataKey: 'componentName',
width: 200
},
{
key: 'status',
title: '状态',
dataKey: 'status',
width: 60,
fixed: TableV2FixedDir.RIGHT,
cellRenderer: ({ rowData }) => {
// 检查权限
if (!checkPermi(['system:menu:update'])) {
return <DictTag type={DICT_TYPE.COMMON_STATUS} value={rowData.status} />
}
// 如果有权限,渲染 ElSwitch
return (
<ElSwitch
v-model={rowData.status}
active-value={CommonStatusEnum.ENABLE}
inactive-value={CommonStatusEnum.DISABLE}
loading={menuStatusUpdating[rowData.id]}
class="ml-4px"
onChange={(val) => handleStatusChanged(rowData, val)}
/>
)
}
},
{
key: 'operations',
title: '操作',
align: 'center',
width: 160,
fixed: TableV2FixedDir.RIGHT,
cellRenderer: ({ rowData }) => {
// 定义按钮列表
const buttons = []
// 检查权限并添加按钮
if (checkPermi(['system:menu:update'])) {
buttons.push(
<ElButton key="edit" link type="primary" onClick={() => openForm('update', rowData.id)}>
修改
</ElButton>
)
}
if (checkPermi(['system:menu:create'])) {
buttons.push(
<ElButton
key="create"
link
type="primary"
onClick={() => openForm('create', undefined, rowData.id)}
>
新增
</ElButton>
)
}
if (checkPermi(['system:menu:delete'])) {
buttons.push(
<ElButton key="delete" link type="danger" onClick={() => handleDelete(rowData.id)}>
删除
</ElButton>
)
}
// 如果没有权限,返回 null
if (buttons.length === 0) {
return null
}
// 渲染按钮列表
return <>{buttons}</>
}
}
]
const { wsCache } = useCache()
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
// 表格的 column 字段
const columns: Column[] = [
{
dataKey: 'name',
title: '菜单名称',
width: 250
},
{
dataKey: 'icon',
title: '图标',
width: 150,
cellRenderer: ({ rowData }) => {
return h(Icon, {
icon: rowData.icon
})
}
},
{
dataKey: 'sort',
title: '排序',
width: 100
},
{
dataKey: 'permission',
title: '权限标识',
width: 240
},
{
dataKey: 'component',
title: '组件路径',
width: 240
},
{
dataKey: 'componentName',
title: '组件名称',
width: 240
},
{
dataKey: 'status',
title: '状态',
width: 160,
cellRenderer: ({ rowData }) => {
return h(ElSwitch, {
modelValue: rowData.status,
activeValue: CommonStatusEnum.ENABLE,
inactiveValue: CommonStatusEnum.DISABLE,
loading: menuStatusUpdating.value[rowData.id],
disabled: !hasPermission(['system:menu:update']),
onChange: (val) => handleStatusChanged(rowData, val as number)
})
}
},
{
dataKey: 'operation',
title: '操作',
width: 200,
cellRenderer: ({ rowData }) => {
return h(
'div',
{},
[
hasPermission(['system:menu:update']) &&
h(
ElButton,
{
link: true,
type: 'primary',
onClick: () => openForm('update', rowData.id)
},
() => '修改'
),
hasPermission(['system:menu:create']) &&
h(
ElButton,
{
link: true,
type: 'primary',
onClick: () => openForm('create', undefined, rowData.id)
},
() => '新增'
),
hasPermission(['system:menu:delete']) &&
h(
ElButton,
{
link: true,
type: 'danger',
onClick: () => handleDelete(rowData.id)
},
() => '删除'
)
].filter(Boolean)
)
}
}
]
const loading = ref(true) // 列表的加载中
const list = ref<any>([]) // 列表的数据
const list = ref<any[]>([]) // 列表的数据
const queryParams = reactive({
name: undefined,
status: undefined
})
const queryFormRef = ref() // 搜索的表单
const isExpandAll = ref(false) // 是否展开,默认全部折叠
const refreshTable = ref(true) // 重新渲染表格状态
// 添加展开行控制
const expandedRowKeys = ref<number[]>([])
/** 查询列表 */
const getList = async () => {
@@ -235,6 +259,18 @@ const openForm = (type: string, id?: number, parentId?: number) => {
formRef.value.open(type, id, parentId)
}
/** 展开/折叠操作 */
const toggleExpandAll = () => {
if (!isExpandAll.value) {
// 展开所有
expandedRowKeys.value = list.value.map((item) => item.id)
} else {
// 折叠所有
expandedRowKeys.value = []
}
isExpandAll.value = !isExpandAll.value
}
/** 刷新菜单缓存按钮操作 */
const refreshMenu = async () => {
try {

View File

@@ -124,6 +124,7 @@
:active-value="0"
:inactive-value="1"
@change="handleStatusChange(scope.row)"
:disabled="!checkPermi(['system:user:update'])"
/>
</template>
</el-table-column>