Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3
commit
eda7d63e4f
|
|
@ -115,7 +115,8 @@ const include = [
|
||||||
'@element-plus/icons-vue',
|
'@element-plus/icons-vue',
|
||||||
'element-plus/es/components/footer/style/css',
|
'element-plus/es/components/footer/style/css',
|
||||||
'element-plus/es/components/empty/style/css',
|
'element-plus/es/components/empty/style/css',
|
||||||
'element-plus/es/components/mention/style/css'
|
'element-plus/es/components/mention/style/css',
|
||||||
|
'element-plus/es/components/progress/style/css'
|
||||||
]
|
]
|
||||||
|
|
||||||
const exclude = ['@iconify/json']
|
const exclude = ['@iconify/json']
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
|
"@wangeditor/plugin-mention": "^1.0.0",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "1.9.0",
|
"axios": "1.9.0",
|
||||||
|
|
@ -65,6 +66,7 @@
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"qs": "^6.12.0",
|
"qs": "^6.12.0",
|
||||||
|
"snabbdom": "^3.6.2",
|
||||||
"sortablejs": "^1.15.3",
|
"sortablejs": "^1.15.3",
|
||||||
"steady-xml": "^0.1.0",
|
"steady-xml": "^0.1.0",
|
||||||
"url": "^0.11.3",
|
"url": "^0.11.3",
|
||||||
|
|
@ -74,6 +76,7 @@
|
||||||
"vue-i18n": "9.10.2",
|
"vue-i18n": "9.10.2",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
|
"vue3-print-nb": "^0.1.4",
|
||||||
"vue3-signature": "^0.2.4",
|
"vue3-signature": "^0.2.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"web-storage-cache": "^1.1.1",
|
"web-storage-cache": "^1.1.1",
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export const ChatMessageApi = {
|
||||||
conversationId,
|
conversationId,
|
||||||
content,
|
content,
|
||||||
useContext: enableContext,
|
useContext: enableContext,
|
||||||
webSearch: enableWebSearch,
|
useSearch: enableWebSearch,
|
||||||
attachmentUrls: attachmentUrls || []
|
attachmentUrls: attachmentUrls || []
|
||||||
}),
|
}),
|
||||||
onmessage: onMessage,
|
onmessage: onMessage,
|
||||||
|
|
|
||||||
|
|
@ -108,3 +108,8 @@ export const getFormFieldsPermission = async (params: any) => {
|
||||||
export const getProcessInstanceBpmnModelView = async (id: string) => {
|
export const getProcessInstanceBpmnModelView = async (id: string) => {
|
||||||
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
|
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取流程实例打印数据
|
||||||
|
export const getProcessInstancePrintData = async (id: string) => {
|
||||||
|
return await request.get({ url: '/bpm/process-instance/get-print-data?processInstanceId=' + id })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import * as FileApi from '@/api/infra/file'
|
import * as FileApi from '@/api/infra/file'
|
||||||
// import CryptoJS from 'crypto-js'
|
|
||||||
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
|
@ -20,7 +19,7 @@ export const useUpload = (directory?: string) => {
|
||||||
// 模式一:前端上传
|
// 模式一:前端上传
|
||||||
if (isClientUpload) {
|
if (isClientUpload) {
|
||||||
// 1.1 生成文件名称
|
// 1.1 生成文件名称
|
||||||
const fileName = await generateFileName(options.file)
|
const fileName = options.file.name || options.filename
|
||||||
// 1.2 获取文件预签名地址
|
// 1.2 获取文件预签名地址
|
||||||
const presignedInfo = await FileApi.getFilePresignedUrl(fileName, directory)
|
const presignedInfo = await FileApi.getFilePresignedUrl(fileName, directory)
|
||||||
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
|
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
|
||||||
|
|
@ -32,7 +31,7 @@ export const useUpload = (directory?: string) => {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 1.4. 记录文件信息到后端(异步)
|
// 1.4. 记录文件信息到后端(异步)
|
||||||
createFile(presignedInfo, options.file)
|
createFile(presignedInfo, options.file, fileName)
|
||||||
// 通知成功,数据格式保持与后端上传的返回结果一致
|
// 通知成功,数据格式保持与后端上传的返回结果一致
|
||||||
return { data: presignedInfo.url }
|
return { data: presignedInfo.url }
|
||||||
})
|
})
|
||||||
|
|
@ -64,15 +63,15 @@ export const useUpload = (directory?: string) => {
|
||||||
/**
|
/**
|
||||||
* 创建文件信息
|
* 创建文件信息
|
||||||
* @param vo 文件预签名信息
|
* @param vo 文件预签名信息
|
||||||
* @param name 文件名称
|
|
||||||
* @param file 文件
|
* @param file 文件
|
||||||
|
* @param fileName
|
||||||
*/
|
*/
|
||||||
function createFile(vo: FileApi.FilePresignedUrlRespVO, file: UploadRawFile) {
|
function createFile(vo: FileApi.FilePresignedUrlRespVO, file: UploadRawFile, fileName: string) {
|
||||||
const fileVo = {
|
const fileVo = {
|
||||||
configId: vo.configId,
|
configId: vo.configId,
|
||||||
url: vo.url,
|
url: vo.url,
|
||||||
path: vo.path,
|
path: vo.path,
|
||||||
name: file.name,
|
name: fileName,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size
|
size: file.size
|
||||||
}
|
}
|
||||||
|
|
@ -80,22 +79,6 @@ function createFile(vo: FileApi.FilePresignedUrlRespVO, file: UploadRawFile) {
|
||||||
return fileVo
|
return fileVo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成文件名称(使用算法SHA256)
|
|
||||||
* @param file 要上传的文件
|
|
||||||
*/
|
|
||||||
async function generateFileName(file: UploadRawFile) {
|
|
||||||
// // 读取文件内容
|
|
||||||
// const data = await file.arrayBuffer()
|
|
||||||
// const wordArray = CryptoJS.lib.WordArray.create(data)
|
|
||||||
// // 计算SHA256
|
|
||||||
// const sha256 = CryptoJS.SHA256(wordArray).toString()
|
|
||||||
// // 拼接后缀
|
|
||||||
// const ext = file.name.substring(file.name.lastIndexOf('.'))
|
|
||||||
// return `${sha256}${ext}`
|
|
||||||
return file.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传类型
|
* 上传类型
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
11
src/main.ts
11
src/main.ts
|
|
@ -42,6 +42,11 @@ import Logger from '@/utils/Logger'
|
||||||
|
|
||||||
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
||||||
|
|
||||||
|
// wangEditor 插件注册
|
||||||
|
import { setupWangEditorPlugin } from '@/views/bpm/model/form/PrintTemplate'
|
||||||
|
|
||||||
|
import print from 'vue3-print-nb' // 打印插件
|
||||||
|
|
||||||
// 创建实例
|
// 创建实例
|
||||||
const setupAll = async () => {
|
const setupAll = async () => {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
@ -62,10 +67,16 @@ const setupAll = async () => {
|
||||||
setupAuth(app)
|
setupAuth(app)
|
||||||
setupMountedFocus(app)
|
setupMountedFocus(app)
|
||||||
|
|
||||||
|
// wangEditor 插件注册
|
||||||
|
setupWangEditorPlugin()
|
||||||
|
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
|
|
||||||
app.use(VueDOMPurifyHTML)
|
app.use(VueDOMPurifyHTML)
|
||||||
|
|
||||||
|
// 打印
|
||||||
|
app.use(print)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,21 @@ const schema = reactive<FormSchema[]>([
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const formRef = ref<FormExpose>() // 表单 Ref
|
const formRef = ref<FormExpose>() // 表单 Ref
|
||||||
|
|
||||||
|
// 监听 userStore 中头像的变化,同步更新表单数据
|
||||||
|
watch(
|
||||||
|
() => userStore.getUser.avatar,
|
||||||
|
(newAvatar) => {
|
||||||
|
if (newAvatar && formRef.value) {
|
||||||
|
// 直接更新表单模型中的头像字段
|
||||||
|
const formModel = formRef.value.formModel
|
||||||
|
if (formModel) {
|
||||||
|
formModel.avatar = newAvatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const elForm = unref(formRef)?.getElFormRef()
|
const elForm = unref(formRef)?.getElFormRef()
|
||||||
if (!elForm) return
|
if (!elForm) return
|
||||||
|
|
@ -87,17 +102,19 @@ const submit = () => {
|
||||||
await updateUserProfile(data)
|
await updateUserProfile(data)
|
||||||
message.success(t('common.updateSuccess'))
|
message.success(t('common.updateSuccess'))
|
||||||
const profile = await init()
|
const profile = await init()
|
||||||
userStore.setUserNicknameAction(profile.nickname)
|
await userStore.setUserNicknameAction(profile.nickname)
|
||||||
// 发送成功事件
|
// 发送成功事件
|
||||||
emit('success')
|
emit('success')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const res = await getUserProfile()
|
const res = await getUserProfile()
|
||||||
unref(formRef)?.setValues(res)
|
unref(formRef)?.setValues(res)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await init()
|
await init()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -49,18 +49,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import UserAvatar from './UserAvatar.vue'
|
import UserAvatar from './UserAvatar.vue'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||||
|
|
||||||
defineOptions({ name: 'ProfileUser' })
|
defineOptions({ name: 'ProfileUser' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
const userInfo = ref({} as ProfileVO)
|
const userInfo = ref({} as ProfileVO)
|
||||||
|
|
||||||
const getUserInfo = async () => {
|
const getUserInfo = async () => {
|
||||||
const users = await getUserProfile()
|
const users = await getUserProfile()
|
||||||
userInfo.value = users
|
userInfo.value = users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听 userStore 中头像的变化,同步更新本地 userInfo
|
||||||
|
watch(
|
||||||
|
() => userStore.getUser.avatar,
|
||||||
|
(newAvatar) => {
|
||||||
|
if (newAvatar && userInfo.value) {
|
||||||
|
userInfo.value.avatar = newAvatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 暴露刷新方法
|
// 暴露刷新方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh: getUserInfo
|
refresh: getUserInfo
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<!-- header -->
|
|
||||||
<template>
|
|
||||||
<el-header class="flex flex-row justify-between items-center px-10px whitespace-nowrap text-ellipsis w-full" :style="{ backgroundColor: 'var(--el-bg-color-page)' }">
|
|
||||||
<div class="text-20px font-bold overflow-hidden max-w-220px" :style="{ color: 'var(--el-text-color-primary)' }">
|
|
||||||
{{ title }}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</el-header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 设置组件属性
|
|
||||||
defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-row flex-wrap relative h-full overflow-auto px-25px pb-140px items-start content-start justify-start"
|
class="flex flex-row flex-wrap relative h-full overflow-auto pb-140px items-start content-start justify-start"
|
||||||
ref="tabsRef"
|
ref="tabsRef"
|
||||||
@scroll="handleTabsScroll"
|
@scroll="handleTabsScroll"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
<!-- chat 角色仓库 -->
|
<!-- chat 角色仓库 -->
|
||||||
<template>
|
<template>
|
||||||
<el-container
|
<el-container class="bg-[var(--el-bg-color)] -mt-25px">
|
||||||
class="role-container absolute w-full h-full m-0 p-0 left-0 right-0 top-0 bottom-0 bg-[var(--el-bg-color)] overflow-hidden flex !flex-col"
|
|
||||||
>
|
|
||||||
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
||||||
<!-- header -->
|
|
||||||
<RoleHeader title="角色仓库" class="relative" />
|
|
||||||
<!-- main -->
|
<!-- main -->
|
||||||
<el-main class="flex-1 overflow-hidden m-0 !p-0 relative">
|
<el-main class="flex-1 overflow-hidden m-0 !p-0 relative">
|
||||||
<div class="mx-5 mt-5 mb-0 absolute right-0 -top-1.25 z-100">
|
<div class="mx-3 mt-3 mb-0 absolute right-0 -top-1.25 z-100">
|
||||||
<!-- 搜索按钮 -->
|
<!-- 搜索按钮 -->
|
||||||
<el-input
|
<el-input
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
|
@ -30,16 +26,8 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- tabs -->
|
<!-- tabs -->
|
||||||
<el-tabs
|
<el-tabs v-model="activeTab" @tab-click="handleTabsClick" class="relative h-full">
|
||||||
v-model="activeTab"
|
<el-tab-pane label="我的角色" name="my-role" class="flex flex-col h-full overflow-y-auto">
|
||||||
@tab-click="handleTabsClick"
|
|
||||||
class="relative h-full [&_.el-tabs__nav-scroll]:my-2.5 [&_.el-tabs__nav-scroll]:mx-5"
|
|
||||||
>
|
|
||||||
<el-tab-pane
|
|
||||||
label="我的角色"
|
|
||||||
name="my-role"
|
|
||||||
class="flex flex-col h-full overflow-y-auto relative"
|
|
||||||
>
|
|
||||||
<RoleList
|
<RoleList
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:role-list="myRoleList"
|
:role-list="myRoleList"
|
||||||
|
|
@ -48,12 +36,12 @@
|
||||||
@on-edit="handlerCardEdit"
|
@on-edit="handlerCardEdit"
|
||||||
@on-use="handlerCardUse"
|
@on-use="handlerCardUse"
|
||||||
@on-page="handlerCardPage('my')"
|
@on-page="handlerCardPage('my')"
|
||||||
class="mt-20px"
|
class="mt-3"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="公共角色" name="public-role">
|
<el-tab-pane label="公共角色" name="public-role" class="!pt-2">
|
||||||
<RoleCategoryList
|
<RoleCategoryList
|
||||||
class="mx-6.75"
|
class="mx-3"
|
||||||
:category-list="categoryList"
|
:category-list="categoryList"
|
||||||
:active="activeCategory"
|
:active="activeCategory"
|
||||||
@on-category-click="handlerCategoryClick"
|
@on-category-click="handlerCategoryClick"
|
||||||
|
|
@ -64,7 +52,7 @@
|
||||||
@on-edit="handlerCardEdit"
|
@on-edit="handlerCardEdit"
|
||||||
@on-use="handlerCardUse"
|
@on-use="handlerCardUse"
|
||||||
@on-page="handlerCardPage('public')"
|
@on-page="handlerCardPage('public')"
|
||||||
class="mt-20px"
|
class="mt-3"
|
||||||
loading
|
loading
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
@ -75,7 +63,6 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import RoleHeader from './RoleHeader.vue'
|
|
||||||
import RoleList from './RoleList.vue'
|
import RoleList from './RoleList.vue'
|
||||||
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
||||||
import RoleCategoryList from './RoleCategoryList.vue'
|
import RoleCategoryList from './RoleCategoryList.vue'
|
||||||
|
|
@ -83,8 +70,11 @@ import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatR
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import { Search } from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import { TabsPaneContext } from 'element-plus'
|
import { TabsPaneContext } from 'element-plus'
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
|
||||||
const router = useRouter() // 路由对象
|
const router = useRouter() // 路由对象
|
||||||
|
const { currentRoute } = useRouter() // 路由
|
||||||
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
|
|
||||||
// 属性定义
|
// 属性定义
|
||||||
const loading = ref<boolean>(false) // 加载中
|
const loading = ref<boolean>(false) // 加载中
|
||||||
|
|
@ -134,7 +124,7 @@ const getPublicRole = async (append?: boolean) => {
|
||||||
name: search.value,
|
name: search.value,
|
||||||
publicStatus: true
|
publicStatus: true
|
||||||
}
|
}
|
||||||
const { total, list } = await ChatRoleApi.getMyPage(params)
|
const { list } = await ChatRoleApi.getMyPage(params)
|
||||||
if (append) {
|
if (append) {
|
||||||
publicRoleList.value.push.apply(publicRoleList.value, list)
|
publicRoleList.value.push.apply(publicRoleList.value, list)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -214,7 +204,8 @@ const handlerCardUse = async (role) => {
|
||||||
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
||||||
|
|
||||||
// 2. 跳转页面
|
// 2. 跳转页面
|
||||||
await router.push({
|
delView(unref(currentRoute))
|
||||||
|
await router.replace({
|
||||||
name: 'AiChat',
|
name: 'AiChat',
|
||||||
query: {
|
query: {
|
||||||
conversationId: conversationId
|
conversationId: conversationId
|
||||||
|
|
@ -233,6 +224,23 @@ onMounted(async () => {
|
||||||
<!-- 覆盖 element plus css -->
|
<!-- 覆盖 element plus css -->
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.el-tabs__nav-scroll {
|
.el-tabs__nav-scroll {
|
||||||
margin: 10px 20px;
|
margin: 2px 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__header {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__nav-wrap {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tab-pane {
|
||||||
|
padding: 8px 0 0 0 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -516,7 +516,7 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
// 异常提示,并停止流
|
// 异常提示,并停止流
|
||||||
message.alert(`对话异常! ${error}`)
|
message.alert(`对话异常!`)
|
||||||
stopStream()
|
stopStream()
|
||||||
// 需要抛出异常,禁止重试
|
// 需要抛出异常,禁止重试
|
||||||
throw error
|
throw error
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<el-form ref="formRef" :model="modelData" label-width="120px" class="mt-20px">
|
<el-form ref="formRef" :model="modelData" label-width="130px" class="mt-20px">
|
||||||
<el-form-item class="mb-20px">
|
<el-form-item class="mb-20px">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-text size="large" tag="b">提交人权限</el-text>
|
<el-text size="large" tag="b">提交人权限</el-text>
|
||||||
|
|
@ -231,7 +231,30 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="mb-20px">
|
||||||
|
<template #label>
|
||||||
|
<el-text size="large" tag="b">自定义打印模板</el-text>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col w-100%">
|
||||||
|
<div class="flex">
|
||||||
|
<el-switch
|
||||||
|
v-model="modelData.printTemplateSetting.enable"
|
||||||
|
@change="handlePrintTemplateEnableChange"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
v-if="modelData.printTemplateSetting.enable"
|
||||||
|
class="ml-80px"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="handleEditPrintTemplate"
|
||||||
|
>
|
||||||
|
编辑模板
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<print-template ref="printTemplateRef" @confirm="confirmPrintTemplate" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -241,6 +264,7 @@ import * as FormApi from '@/api/bpm/form'
|
||||||
import { parseFormFields } from '@/components/FormCreate/src/utils'
|
import { parseFormFields } from '@/components/FormCreate/src/utils'
|
||||||
import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
|
import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
|
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
|
||||||
|
import PrintTemplate from './PrintTemplate/Index.vue'
|
||||||
|
|
||||||
const modelData = defineModel<any>()
|
const modelData = defineModel<any>()
|
||||||
|
|
||||||
|
|
@ -394,6 +418,7 @@ const formFieldOptions4Summary = computed(() => {
|
||||||
const unParsedFormFields = ref<string[]>([])
|
const unParsedFormFields = ref<string[]>([])
|
||||||
/** 暴露给子组件 HttpRequestSetting 使用 */
|
/** 暴露给子组件 HttpRequestSetting 使用 */
|
||||||
provide('formFields', unParsedFormFields)
|
provide('formFields', unParsedFormFields)
|
||||||
|
provide('formFieldsObj', formFields)
|
||||||
|
|
||||||
/** 兼容以前未配置更多设置的流程 */
|
/** 兼容以前未配置更多设置的流程 */
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
|
|
@ -436,6 +461,11 @@ const initData = () => {
|
||||||
if (modelData.value.allowWithdrawTask) {
|
if (modelData.value.allowWithdrawTask) {
|
||||||
modelData.value.allowWithdrawTask = false
|
modelData.value.allowWithdrawTask = false
|
||||||
}
|
}
|
||||||
|
if (!modelData.value.printTemplateSetting) {
|
||||||
|
modelData.value.printTemplateSetting = {
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ initData })
|
defineExpose({ initData })
|
||||||
|
|
||||||
|
|
@ -460,4 +490,21 @@ watch(
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultTemplate =
|
||||||
|
'<p style="text-align: center;"><span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="流程名称" data-info="%7B%22id%22%3A%22processName%22%7D">@流程名称</span></p><p style="text-align: right;">打印人:<span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="打印人" data-info="%7B%22id%22%3A%22printUser%22%7D">@打印人</span></p><p style="text-align: right;">流程编号:<span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="流程编号" data-info="%7B%22id%22%3A%22processNum%22%7D">@流程编号</span> 打印时间:<span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="打印时间" data-info="%7B%22id%22%3A%22printTime%22%7D">@打印时间</span></p><table style="width: 100%;"><tbody><tr><td colSpan="1" rowSpan="1" width="auto">发起人</td><td colSpan="1" rowSpan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="发起人" data-info="%7B%22id%22%3A%22startUser%22%7D">@发起人</span></td><td colSpan="1" rowSpan="1" width="auto">发起时间</td><td colSpan="1" rowSpan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="发起时间" data-info="%7B%22id%22%3A%22startTime%22%7D">@发起时间</span></td></tr><tr><td colSpan="1" rowSpan="1" width="auto">所属部门</td><td colSpan="1" rowSpan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="发起人部门" data-info="%7B%22id%22%3A%22startUserDept%22%7D">@发起人部门</span></td><td colSpan="1" rowSpan="1" width="auto">流程状态</td><td colSpan="1" rowSpan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void data-w-e-is-inline data-value="流程状态" data-info="%7B%22id%22%3A%22processStatus%22%7D">@流程状态</span></td></tr></tbody></table><p><span data-w-e-type="process-record" data-w-e-is-void data-w-e-is-inline>流程记录</span></p>'
|
||||||
|
const handlePrintTemplateEnableChange = (val: boolean) => {
|
||||||
|
if (val) {
|
||||||
|
if (!modelData.value.printTemplateSetting.template) {
|
||||||
|
modelData.value.printTemplateSetting.template = defaultTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const printTemplateRef = ref()
|
||||||
|
const handleEditPrintTemplate = () => {
|
||||||
|
printTemplateRef.value.open(modelData.value.printTemplateSetting.template)
|
||||||
|
}
|
||||||
|
const confirmPrintTemplate = (template: any) => {
|
||||||
|
modelData.value.printTemplateSetting.template = template
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
|
import { IDomEditor } from '@wangeditor/editor'
|
||||||
|
import MentionModal from './MentionModal.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm'])
|
||||||
|
|
||||||
|
// @mention 相关
|
||||||
|
const isShowModal = ref(false)
|
||||||
|
const showModal = () => {
|
||||||
|
isShowModal.value = true
|
||||||
|
}
|
||||||
|
const hideModal = () => {
|
||||||
|
isShowModal.value = false
|
||||||
|
}
|
||||||
|
const insertMention = (id: any, name: any) => {
|
||||||
|
const mentionNode = {
|
||||||
|
type: 'mention',
|
||||||
|
value: name,
|
||||||
|
info: { id },
|
||||||
|
children: [{ text: '' }]
|
||||||
|
}
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor) {
|
||||||
|
editor.restoreSelection()
|
||||||
|
editor.deleteBackward('character')
|
||||||
|
editor.insertNode(mentionNode)
|
||||||
|
editor.move(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog 相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const open = async (template: string) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
valueHtml.value = template
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
const handleConfirm = () => {
|
||||||
|
emit('confirm', valueHtml.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editor 相关
|
||||||
|
const editorRef = shallowRef<IDomEditor>()
|
||||||
|
const editorId = ref('wangeEditor-1')
|
||||||
|
const toolbarConfig = {
|
||||||
|
excludeKeys: ['group-video'],
|
||||||
|
insertKeys: {
|
||||||
|
index: 31,
|
||||||
|
keys: ['ProcessRecordMenu']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const editorConfig = {
|
||||||
|
placeholder: '请输入内容...',
|
||||||
|
EXTEND_CONF: {
|
||||||
|
mentionConfig: {
|
||||||
|
showModal,
|
||||||
|
hideModal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const valueHtml = ref()
|
||||||
|
const handleCreated = (editor: IDomEditor) => {
|
||||||
|
editorRef.value = editor
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editor.destroy()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" title="自定义模板" fullscreen>
|
||||||
|
<div style="margin: 0 10px">
|
||||||
|
<el-alert
|
||||||
|
title="输入 @ 可选择插入流程表单选项和默认选项"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- TODO @unocss 简化 style -->
|
||||||
|
<div style="border: 1px solid #ccc; margin: 10px">
|
||||||
|
<Toolbar
|
||||||
|
style="border-bottom: 1px solid #ccc"
|
||||||
|
:editor="editorRef"
|
||||||
|
:editorId="editorId"
|
||||||
|
:defaultConfig="toolbarConfig"
|
||||||
|
/>
|
||||||
|
<Editor
|
||||||
|
style="height: 500px; overflow-y: hidden"
|
||||||
|
v-model="valueHtml"
|
||||||
|
:defaultConfig="editorConfig"
|
||||||
|
:editorId="editorId"
|
||||||
|
@on-created="handleCreated"
|
||||||
|
/>
|
||||||
|
<MentionModal
|
||||||
|
v-if="isShowModal"
|
||||||
|
@hide-mention-modal="hideModal"
|
||||||
|
@insert-mention="insertMention"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="margin-right: 10px; float: right">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style src="@wangeditor/editor/dist/css/style.css"></style>
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const emit = defineEmits(['hideMentionModal', 'insertMention'])
|
||||||
|
|
||||||
|
const inputRef = ref()
|
||||||
|
const top = ref('')
|
||||||
|
const left = ref('')
|
||||||
|
const searchVal = ref('')
|
||||||
|
const list = ref([
|
||||||
|
{ id: 'startUser', name: '发起人' },
|
||||||
|
{ id: 'startUserDept', name: '发起人部门' },
|
||||||
|
{ id: 'processName', name: '流程名称' },
|
||||||
|
{ id: 'processNum', name: '流程编号' },
|
||||||
|
{ id: 'startTime', name: '发起时间' },
|
||||||
|
{ id: 'endTime', name: '结束时间' },
|
||||||
|
{ id: 'processStatus', name: '流程状态' },
|
||||||
|
{ id: 'printUser', name: '打印人' },
|
||||||
|
{ id: 'printTime', name: '打印时间' }
|
||||||
|
])
|
||||||
|
const searchedList = computed(() => {
|
||||||
|
const searchValStr = searchVal.value.trim().toLowerCase()
|
||||||
|
return list.value.filter((item) => {
|
||||||
|
const name = item.name.toLowerCase()
|
||||||
|
return name.indexOf(searchValStr) >= 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const inputKeyupHandler = (event: any) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
emit('hideMentionModal')
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
const firstOne = searchedList.value[0]
|
||||||
|
if (firstOne) {
|
||||||
|
const { id, name } = firstOne
|
||||||
|
insertMentionHandler(id, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const insertMentionHandler = (id: any, name: any) => {
|
||||||
|
emit('insertMention', id, name)
|
||||||
|
emit('hideMentionModal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const formFields = inject<any>('formFieldsObj')
|
||||||
|
onMounted(() => {
|
||||||
|
if (formFields.value && formFields.value.length > 0) {
|
||||||
|
const cloneFormField = formFields.value.map((item) => {
|
||||||
|
return {
|
||||||
|
name: '[表单]' + item.title,
|
||||||
|
id: item.field
|
||||||
|
}
|
||||||
|
})
|
||||||
|
list.value.push(...cloneFormField)
|
||||||
|
}
|
||||||
|
const domSelection = document.getSelection()
|
||||||
|
const domRange = domSelection?.getRangeAt(0)
|
||||||
|
if (domRange == null) return
|
||||||
|
const rect = domRange.getBoundingClientRect()
|
||||||
|
|
||||||
|
top.value = `${rect.top + 20}px`
|
||||||
|
left.value = `${rect.left + 5}px`
|
||||||
|
|
||||||
|
inputRef.value.focus()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="mention-modal" :style="{ top: top, left: left }">
|
||||||
|
<!-- TODO @lesan:css 可以用 unocss 哇? -->
|
||||||
|
<input id="mention-input" v-model="searchVal" ref="inputRef" @keyup="inputKeyupHandler" />
|
||||||
|
<ul id="mention-list">
|
||||||
|
<li
|
||||||
|
v-for="item in searchedList"
|
||||||
|
:key="item.id"
|
||||||
|
@click="insertMentionHandler(item.id, item.name)"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#mention-modal {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mention-modal input {
|
||||||
|
width: 100px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mention-modal ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mention-modal ul li {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 3px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mention-modal ul li:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Boot } from '@wangeditor/editor'
|
||||||
|
import processRecordModule from './module'
|
||||||
|
import mentionModule from '@wangeditor/plugin-mention'
|
||||||
|
|
||||||
|
// 注册:要在创建编辑器之前注册,且只能注册一次,不可重复注册
|
||||||
|
export const setupWangEditorPlugin = () => {
|
||||||
|
Boot.registerModule(processRecordModule)
|
||||||
|
Boot.registerModule(mentionModule)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { SlateElement } from '@wangeditor/editor'
|
||||||
|
|
||||||
|
function processRecordToHtml(_elem: SlateElement, _childrenHtml: string): string {
|
||||||
|
return `<span data-w-e-type="process-record" data-w-e-is-void data-w-e-is-inline>流程记录</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const conf = {
|
||||||
|
type: 'process-record',
|
||||||
|
elemToHtml: processRecordToHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
export default conf
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IModuleConf } from '@wangeditor/editor'
|
||||||
|
import withProcessRecord from './plugin'
|
||||||
|
import renderElemConf from './render-elem'
|
||||||
|
import elemToHtmlConf from './elem-to-html'
|
||||||
|
import parseHtmlConf from './parse-elem-html'
|
||||||
|
import processRecordMenu from './menu/ProcessRecordMenu'
|
||||||
|
|
||||||
|
// 可参考 wangEditor 官方文档进行自定义扩展插件:https://www.wangeditor.com/v5/development.html#%E5%AE%9A%E4%B9%89%E6%96%B0%E5%85%83%E7%B4%A0
|
||||||
|
const module: Partial<IModuleConf> = {
|
||||||
|
editorPlugin: withProcessRecord,
|
||||||
|
renderElems: [renderElemConf],
|
||||||
|
elemsToHtml: [elemToHtmlConf],
|
||||||
|
parseElemsHtml: [parseHtmlConf],
|
||||||
|
menus: [processRecordMenu]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default module
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { IButtonMenu, IDomEditor } from '@wangeditor/editor'
|
||||||
|
|
||||||
|
class ProcessRecordMenu implements IButtonMenu {
|
||||||
|
readonly tag: string
|
||||||
|
readonly title: string
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.title = '流程记录'
|
||||||
|
this.tag = 'button'
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(_editor: IDomEditor): string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive(_editor: IDomEditor): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled(_editor: IDomEditor): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(editor: IDomEditor, _value: string) {
|
||||||
|
if (this.isDisabled(editor)) return
|
||||||
|
const processRecordElem = {
|
||||||
|
type: 'process-record',
|
||||||
|
children: [{ text: '' }]
|
||||||
|
}
|
||||||
|
editor.insertNode(processRecordElem)
|
||||||
|
editor.move(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProcessRecordMenuConf = {
|
||||||
|
key: 'ProcessRecordMenu',
|
||||||
|
factory() {
|
||||||
|
return new ProcessRecordMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProcessRecordMenuConf
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { DOMElement } from './utils/dom'
|
||||||
|
import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor/editor'
|
||||||
|
|
||||||
|
function parseHtml(
|
||||||
|
_elem: DOMElement,
|
||||||
|
_children: SlateDescendant[],
|
||||||
|
_editor: IDomEditor
|
||||||
|
): SlateElement {
|
||||||
|
return {
|
||||||
|
// TODO @lesan:这里有个红色告警,可以去掉哇?
|
||||||
|
type: 'process-record',
|
||||||
|
children: [{ text: '' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseHtmlConf = {
|
||||||
|
selector: 'span[data-w-e-type="process-record"]',
|
||||||
|
parseElemHtml: parseHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseHtmlConf
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { DomEditor, IDomEditor } from '@wangeditor/editor'
|
||||||
|
|
||||||
|
function withProcessRecord<T extends IDomEditor>(editor: T) {
|
||||||
|
const { isInline, isVoid } = editor
|
||||||
|
const newEditor = editor
|
||||||
|
|
||||||
|
newEditor.isInline = (elem) => {
|
||||||
|
const type = DomEditor.getNodeType(elem)
|
||||||
|
if (type === 'process-record') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isInline(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
newEditor.isVoid = (elem) => {
|
||||||
|
const type = DomEditor.getNodeType(elem)
|
||||||
|
if (type === 'process-record') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVoid(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEditor
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withProcessRecord
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { h, VNode } from 'snabbdom'
|
||||||
|
import { DomEditor, IDomEditor, SlateElement } from '@wangeditor/editor'
|
||||||
|
|
||||||
|
function renderProcessRecord(
|
||||||
|
elem: SlateElement,
|
||||||
|
_children: VNode[] | null,
|
||||||
|
editor: IDomEditor
|
||||||
|
): VNode {
|
||||||
|
const selected = DomEditor.isNodeSelected(editor, elem)
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'table',
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
contentEditable: false
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
border: selected ? '2px solid var(--w-e-textarea-selected-border-color)' : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('thead', [h('tr', [h('th', { attrs: { colSpan: 3 } }, '流程记录')])]),
|
||||||
|
h('tbody', [
|
||||||
|
h('tr', [
|
||||||
|
h('td', [
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
contentEditable: false
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
marginLeft: '3px',
|
||||||
|
marginRight: '3px',
|
||||||
|
backgroundColor: 'var(--w-e-textarea-slight-bg-color)',
|
||||||
|
borderRadius: '3px',
|
||||||
|
padding: '0 3px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`节点`
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
h('td', [
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
contentEditable: false
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
marginLeft: '3px',
|
||||||
|
marginRight: '3px',
|
||||||
|
backgroundColor: 'var(--w-e-textarea-slight-bg-color)',
|
||||||
|
borderRadius: '3px',
|
||||||
|
padding: '0 3px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`操作`
|
||||||
|
)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const conf = {
|
||||||
|
type: 'process-record',
|
||||||
|
renderElem: renderProcessRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
export default conf
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import $, { append, on, hide, click } from 'dom7'
|
||||||
|
|
||||||
|
if (hide) $.fn.hide = hide
|
||||||
|
if (append) $.fn.append = append
|
||||||
|
if (click) $.fn.click = click
|
||||||
|
if (on) $.fn.on = on
|
||||||
|
|
||||||
|
export { Dom7Array } from 'dom7'
|
||||||
|
export default $
|
||||||
|
|
||||||
|
// COMPAT: This is required to prevent TypeScript aliases from doing some very
|
||||||
|
// weird things for Slate's types with the same name as globals. (2019/11/27)
|
||||||
|
// https://github.com/microsoft/TypeScript/issues/35002
|
||||||
|
import DOMNode = globalThis.Node
|
||||||
|
import DOMComment = globalThis.Comment
|
||||||
|
import DOMElement = globalThis.Element
|
||||||
|
import DOMText = globalThis.Text
|
||||||
|
import DOMRange = globalThis.Range
|
||||||
|
import DOMSelection = globalThis.Selection
|
||||||
|
import DOMStaticRange = globalThis.StaticRange
|
||||||
|
export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange }
|
||||||
|
|
@ -174,7 +174,10 @@ const formData: any = ref({
|
||||||
enable: false,
|
enable: false,
|
||||||
summary: []
|
summary: []
|
||||||
},
|
},
|
||||||
allowWithdrawTask: false
|
allowWithdrawTask: false,
|
||||||
|
printTemplateSetting: {
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 流程数据
|
// 流程数据
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||||
|
import { decodeFields } from '@/utils/formCreate'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const printData = ref()
|
||||||
|
const userName = computed(() => userStore.user.nickname ?? '')
|
||||||
|
const printTime = ref(formatDate(new Date(), 'YYYY-MM-DD HH:mm'))
|
||||||
|
const formFields = ref()
|
||||||
|
const printDataMap = ref({})
|
||||||
|
|
||||||
|
const open = async (id: string) => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
printData.value = await ProcessInstanceApi.getProcessInstancePrintData(id)
|
||||||
|
initPrintDataMap()
|
||||||
|
parseFormFields()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
const parseFormFields = () => {
|
||||||
|
const formFieldsObj = decodeFields(printData.value.processInstance.processDefinition.formFields)
|
||||||
|
const processVariables = printData.value.processInstance.formVariables
|
||||||
|
let res: any = []
|
||||||
|
for (const item of formFieldsObj) {
|
||||||
|
const id = item['field']
|
||||||
|
const name = item['title']
|
||||||
|
const variable = processVariables[item['field']]
|
||||||
|
let html = variable
|
||||||
|
switch (item['type']) {
|
||||||
|
case 'UploadImg': {
|
||||||
|
let imgEl = document.createElement('img')
|
||||||
|
imgEl.setAttribute('src', variable)
|
||||||
|
imgEl.setAttribute('style', 'max-width: 600px;')
|
||||||
|
html = imgEl.outerHTML
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'radio':
|
||||||
|
case 'checkbox':
|
||||||
|
case 'select': {
|
||||||
|
const options = item['options'] || []
|
||||||
|
const temp: any = []
|
||||||
|
if (Array.isArray(variable)) {
|
||||||
|
const labels = options.filter((o) => variable.includes(o.value)).map((o) => o.label)
|
||||||
|
temp.push(...labels)
|
||||||
|
} else {
|
||||||
|
const opt = options.find((o) => o.value === variable)
|
||||||
|
temp.push(opt.label)
|
||||||
|
}
|
||||||
|
html = temp.join(',')
|
||||||
|
}
|
||||||
|
// TODO 更多表单打印展示
|
||||||
|
}
|
||||||
|
printDataMap.value[item['field']] = html
|
||||||
|
res.push({ id, name, html })
|
||||||
|
}
|
||||||
|
formFields.value = res
|
||||||
|
}
|
||||||
|
|
||||||
|
const initPrintDataMap = () => {
|
||||||
|
printDataMap.value['startUser'] = printData.value.processInstance.startUser.nickname
|
||||||
|
printDataMap.value['startUserDept'] = printData.value.processInstance.startUser.deptName
|
||||||
|
printDataMap.value['processName'] = printData.value.processInstance.name
|
||||||
|
printDataMap.value['processNum'] = printData.value.processInstance.id
|
||||||
|
printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime)
|
||||||
|
printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime)
|
||||||
|
printDataMap.value['processStatus'] = getDictLabel(
|
||||||
|
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||||
|
printData.value.processInstance.status
|
||||||
|
)
|
||||||
|
printDataMap.value['printUser'] = userName.value
|
||||||
|
printDataMap.value['printTime'] = printTime.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrintTemplateHTML = () => {
|
||||||
|
const parser = new DOMParser()
|
||||||
|
let doc = parser.parseFromString(printData.value.printTemplateHtml, 'text/html')
|
||||||
|
// table 添加border
|
||||||
|
let tables = doc.querySelectorAll('table')
|
||||||
|
tables.forEach((item) => {
|
||||||
|
item.setAttribute('border', '1')
|
||||||
|
item.setAttribute('style', (item.getAttribute('style') || '') + 'border-collapse:collapse;')
|
||||||
|
})
|
||||||
|
// 替换 mentions
|
||||||
|
let mentions = doc.querySelectorAll('[data-w-e-type="mention"]')
|
||||||
|
mentions.forEach((item) => {
|
||||||
|
const mentionId = JSON.parse(decodeURIComponent(item.getAttribute('data-info') ?? ''))['id']
|
||||||
|
item.innerHTML = printDataMap.value[mentionId] ?? ''
|
||||||
|
})
|
||||||
|
// 替换流程记录
|
||||||
|
let processRecords = doc.querySelectorAll('[data-w-e-type="process-record"]')
|
||||||
|
let processRecordTable: Element = document.createElement('table')
|
||||||
|
if (processRecords.length > 0) {
|
||||||
|
// 构建流程记录html
|
||||||
|
processRecordTable.setAttribute('border', '1')
|
||||||
|
processRecordTable.setAttribute('style', 'width:100%;border-collapse:collapse;')
|
||||||
|
const headTr = document.createElement('tr')
|
||||||
|
const headTd = document.createElement('td')
|
||||||
|
headTd.setAttribute('colspan', '2')
|
||||||
|
headTd.setAttribute('width', 'auto')
|
||||||
|
headTd.setAttribute('style', 'text-align: center;')
|
||||||
|
headTd.innerHTML = '流程节点'
|
||||||
|
headTr.appendChild(headTd)
|
||||||
|
processRecordTable.appendChild(headTr)
|
||||||
|
printData.value.tasks.forEach((item) => {
|
||||||
|
const tr = document.createElement('tr')
|
||||||
|
const td1 = document.createElement('td')
|
||||||
|
td1.innerHTML = item.name
|
||||||
|
const td2 = document.createElement('td')
|
||||||
|
td2.innerHTML = item.description
|
||||||
|
tr.appendChild(td1)
|
||||||
|
tr.appendChild(td2)
|
||||||
|
processRecordTable.appendChild(tr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
processRecords.forEach((item) => {
|
||||||
|
item.innerHTML = processRecordTable.outerHTML
|
||||||
|
})
|
||||||
|
// 返回 html
|
||||||
|
return doc.body.innerHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
const printObj = ref({
|
||||||
|
id: 'printDivTag',
|
||||||
|
popTitle: ' ',
|
||||||
|
extraCss: '/print.css',
|
||||||
|
extraHead: '',
|
||||||
|
zIndex: 20003
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog v-loading="loading" v-model="visible" :show-close="false">
|
||||||
|
<div id="printDivTag" style="word-break: break-all">
|
||||||
|
<div v-if="printData.printTemplateEnable" v-html="getPrintTemplateHTML()"></div>
|
||||||
|
<div v-else>
|
||||||
|
<h2 class="text-center">{{ printData.processInstance.name }}</h2>
|
||||||
|
<div class="text-right text-15px">{{ '打印人员: ' + userName }}</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="text-15px">{{ '流程编号: ' + printData.processInstance.id }}</div>
|
||||||
|
<div class="text-15px">{{ '打印时间: ' + printTime }}</div>
|
||||||
|
</div>
|
||||||
|
<table class="mt-20px w-100%" border="1" style="border-collapse: collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="p-5px w-25%">发起人</td>
|
||||||
|
<td class="p-5px w-25%">{{ printData.processInstance.startUser.nickname }}</td>
|
||||||
|
<td class="p-5px w-25%">发起时间</td>
|
||||||
|
<td class="p-5px w-25%">{{ formatDate(printData.processInstance.startTime) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="p-5px w-25%">所属部门</td>
|
||||||
|
<td class="p-5px w-25%">{{ printData.processInstance.startUser.deptName }}</td>
|
||||||
|
<td class="p-5px w-25%">流程状态</td>
|
||||||
|
<td class="p-5px w-25%">
|
||||||
|
{{
|
||||||
|
getDictLabel(
|
||||||
|
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||||
|
printData.processInstance.status
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="p-5px w-100% text-center" colspan="4">
|
||||||
|
<h4>表单内容</h4>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="item in formFields" :key="item.id">
|
||||||
|
<td class="p-5px w-20%">
|
||||||
|
{{ item.name }}
|
||||||
|
</td>
|
||||||
|
<td class="p-5px w-80%" colspan="3">
|
||||||
|
<div v-html="item.html"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="p-5px w-100% text-center" colspan="4">
|
||||||
|
<h4>流程节点</h4>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="item in printData.tasks" :key="item.id">
|
||||||
|
<td class="p-5px w-20%">
|
||||||
|
{{ item.name }}
|
||||||
|
</td>
|
||||||
|
<td class="p-5px w-80%" colspan="3">
|
||||||
|
{{ item.description }}
|
||||||
|
<div v-if="item.signPicUrl && item.signPicUrl.length > 0">
|
||||||
|
<img class="w-90px h-40px" :src="item.signPicUrl" alt="" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="visible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" v-print="printObj"> 打 印</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 修复打印只显示一页 */
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
size: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
html,
|
||||||
|
div {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
:src="auditIconsMap[processInstance.status]"
|
:src="auditIconsMap[processInstance.status]"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
|
<div class="flex">
|
||||||
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
<div class="text-#878c93 h-15px">编号:{{ id }}</div>
|
||||||
|
<Icon icon="ep:printer" class="ml-15px cursor-pointer" @click="handlePrint" />
|
||||||
|
</div>
|
||||||
<el-divider class="!my-8px" />
|
<el-divider class="!my-8px" />
|
||||||
<div class="flex items-center gap-5 mb-10px h-40px">
|
<div class="flex items-center gap-5 mb-10px h-40px">
|
||||||
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
|
||||||
|
|
@ -125,6 +128,9 @@
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 打印预览弹窗 -->
|
||||||
|
<PrintDialog ref="printRef" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
|
@ -146,6 +152,7 @@ import runningSvg from '@/assets/svgs/bpm/running.svg'
|
||||||
import approveSvg from '@/assets/svgs/bpm/approve.svg'
|
import approveSvg from '@/assets/svgs/bpm/approve.svg'
|
||||||
import rejectSvg from '@/assets/svgs/bpm/reject.svg'
|
import rejectSvg from '@/assets/svgs/bpm/reject.svg'
|
||||||
import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
|
import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
|
||||||
|
import PrintDialog from './PrintDialog.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -187,6 +194,7 @@ const getDetail = () => {
|
||||||
/** 加载流程实例 */
|
/** 加载流程实例 */
|
||||||
const BusinessFormComponent = ref<any>(null) // 异步组件
|
const BusinessFormComponent = ref<any>(null) // 异步组件
|
||||||
/** 获取审批详情 */
|
/** 获取审批详情 */
|
||||||
|
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
|
||||||
const getApprovalDetail = async () => {
|
const getApprovalDetail = async () => {
|
||||||
processInstanceLoading.value = true
|
processInstanceLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -265,11 +273,7 @@ const getProcessModelView = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 审批节点信息
|
/** 设置表单权限 */
|
||||||
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
|
|
||||||
/**
|
|
||||||
* 设置表单权限
|
|
||||||
*/
|
|
||||||
const setFieldPermission = (field: string, permission: string) => {
|
const setFieldPermission = (field: string, permission: string) => {
|
||||||
if (permission === FieldPermissionType.READ) {
|
if (permission === FieldPermissionType.READ) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
|
@ -287,14 +291,18 @@ const setFieldPermission = (field: string, permission: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 操作成功后刷新 */
|
||||||
* 操作成功后刷新
|
|
||||||
*/
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
// 重新获取详情
|
// 重新获取详情
|
||||||
getDetail()
|
getDetail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 处理打印 */
|
||||||
|
const printRef = ref()
|
||||||
|
const handlePrint = async () => {
|
||||||
|
printRef.value.open(props.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 当前的 Tab */
|
/** 当前的 Tab */
|
||||||
const activeTab = ref('form')
|
const activeTab = ref('form')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,6 @@ const authDialogVisible = ref(false) // 定义设备认证信息弹框的可见
|
||||||
const authPasswordVisible = ref(false) // 定义密码可见性状态
|
const authPasswordVisible = ref(false) // 定义密码可见性状态
|
||||||
const authInfo = ref<IotDeviceAuthInfoVO>({} as IotDeviceAuthInfoVO) // 定义设备认证信息对象
|
const authInfo = ref<IotDeviceAuthInfoVO>({} as IotDeviceAuthInfoVO) // 定义设备认证信息对象
|
||||||
|
|
||||||
// TODO @AI:注释使用 /** */ 风格,方法注释;
|
|
||||||
/** 控制地图显示的标志 */
|
/** 控制地图显示的标志 */
|
||||||
const showMap = computed(() => {
|
const showMap = computed(() => {
|
||||||
return !!(device.longitude && device.latitude)
|
return !!(device.longitude && device.latitude)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
<!-- 值输入组件 -->
|
<!-- 值输入组件 -->
|
||||||
<!-- TODO @yunai:这个需要在看看。。。 -->
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full min-w-0">
|
<div class="w-full min-w-0">
|
||||||
<!-- 布尔值选择 -->
|
<!-- 布尔值选择 -->
|
||||||
<el-select
|
<el-select
|
||||||
v-if="propertyType === 'bool'"
|
v-if="propertyType === IoTDataSpecsDataTypeEnum.BOOL"
|
||||||
v-model="localValue"
|
v-model="localValue"
|
||||||
placeholder="请选择布尔值"
|
placeholder="请选择布尔值"
|
||||||
@change="handleChange"
|
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
>
|
>
|
||||||
<el-option label="真 (true)" value="true" />
|
<el-option label="真 (true)" value="true" />
|
||||||
<el-option label="假 (false)" value="false" />
|
<el-option label="假 (false)" value="false" />
|
||||||
|
|
@ -17,12 +14,10 @@
|
||||||
|
|
||||||
<!-- 枚举值选择 -->
|
<!-- 枚举值选择 -->
|
||||||
<el-select
|
<el-select
|
||||||
v-else-if="propertyType === 'enum' && enumOptions.length > 0"
|
v-else-if="propertyType === IoTDataSpecsDataTypeEnum.ENUM && enumOptions.length > 0"
|
||||||
v-model="localValue"
|
v-model="localValue"
|
||||||
placeholder="请选择枚举值"
|
placeholder="请选择枚举值"
|
||||||
@change="handleChange"
|
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="option in enumOptions"
|
v-for="option in enumOptions"
|
||||||
|
|
@ -34,9 +29,8 @@
|
||||||
|
|
||||||
<!-- 范围输入 (between 操作符) -->
|
<!-- 范围输入 (between 操作符) -->
|
||||||
<div
|
<div
|
||||||
v-else-if="operator === 'between'"
|
v-else-if="operator === IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value"
|
||||||
class="w-full! flex items-center gap-8px"
|
class="w-full! flex items-center gap-8px"
|
||||||
style="width: 100% !important"
|
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="rangeStart"
|
v-model="rangeStart"
|
||||||
|
|
@ -53,19 +47,15 @@
|
||||||
placeholder="最大值"
|
placeholder="最大值"
|
||||||
@input="handleRangeChange"
|
@input="handleRangeChange"
|
||||||
class="flex-1 min-w-0"
|
class="flex-1 min-w-0"
|
||||||
style="width: auto !important"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 列表输入 (in 操作符) -->
|
<!-- 列表输入 (in 操作符) -->
|
||||||
<div v-else-if="operator === 'in'" class="w-full!" style="width: 100% !important">
|
<div
|
||||||
<el-input
|
v-else-if="operator === IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value"
|
||||||
v-model="localValue"
|
|
||||||
placeholder="请输入值列表,用逗号分隔"
|
|
||||||
@input="handleChange"
|
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
>
|
>
|
||||||
|
<el-input v-model="localValue" placeholder="请输入值列表,用逗号分隔" class="w-full!">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<el-tooltip content="多个值用逗号分隔,如:1,2,3" placement="top">
|
<el-tooltip content="多个值用逗号分隔,如:1,2,3" placement="top">
|
||||||
<Icon
|
<Icon
|
||||||
|
|
@ -85,7 +75,7 @@
|
||||||
|
|
||||||
<!-- 日期时间输入 -->
|
<!-- 日期时间输入 -->
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-else-if="propertyType === 'date'"
|
v-else-if="propertyType === IoTDataSpecsDataTypeEnum.DATE"
|
||||||
v-model="dateValue"
|
v-model="dateValue"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="请选择日期时间"
|
placeholder="请选择日期时间"
|
||||||
|
|
@ -93,7 +83,6 @@
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
@change="handleDateChange"
|
@change="handleDateChange"
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 数字输入 -->
|
<!-- 数字输入 -->
|
||||||
|
|
@ -107,7 +96,6 @@
|
||||||
placeholder="请输入数值"
|
placeholder="请输入数值"
|
||||||
@change="handleNumberChange"
|
@change="handleNumberChange"
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 文本输入 -->
|
<!-- 文本输入 -->
|
||||||
|
|
@ -116,9 +104,7 @@
|
||||||
v-model="localValue"
|
v-model="localValue"
|
||||||
:type="getInputType()"
|
:type="getInputType()"
|
||||||
:placeholder="getPlaceholder()"
|
:placeholder="getPlaceholder()"
|
||||||
@input="handleChange"
|
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
style="width: 100% !important"
|
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
|
|
@ -126,9 +112,9 @@
|
||||||
:content="`单位:${propertyConfig.unit}`"
|
:content="`单位:${propertyConfig.unit}`"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<span class="text-12px text-[var(--el-text-color-secondary)] px-4px">{{
|
<span class="text-12px text-[var(--el-text-color-secondary)] px-4px">
|
||||||
propertyConfig.unit
|
{{ propertyConfig.unit }}
|
||||||
}}</span>
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
@ -137,7 +123,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { IoTDataSpecsDataTypeEnum } from '@/views/iot/utils/constants'
|
import {
|
||||||
|
IoTDataSpecsDataTypeEnum,
|
||||||
|
IotRuleSceneTriggerConditionParameterOperatorEnum
|
||||||
|
} from '@/views/iot/utils/constants'
|
||||||
|
|
||||||
/** 值输入组件 */
|
/** 值输入组件 */
|
||||||
defineOptions({ name: 'ValueInput' })
|
defineOptions({ name: 'ValueInput' })
|
||||||
|
|
@ -165,7 +154,7 @@ const rangeEnd = ref('') // 范围结束值
|
||||||
const dateValue = ref('') // 日期值
|
const dateValue = ref('') // 日期值
|
||||||
const numberValue = ref<number>() // 数字值
|
const numberValue = ref<number>() // 数字值
|
||||||
|
|
||||||
// 计算属性:枚举选项
|
/** 计算属性:枚举选项 */
|
||||||
const enumOptions = computed(() => {
|
const enumOptions = computed(() => {
|
||||||
if (props.propertyConfig?.enum) {
|
if (props.propertyConfig?.enum) {
|
||||||
return props.propertyConfig.enum.map((item: any) => ({
|
return props.propertyConfig.enum.map((item: any) => ({
|
||||||
|
|
@ -176,9 +165,12 @@ const enumOptions = computed(() => {
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算属性:列表预览
|
/** 计算属性:列表预览 */
|
||||||
const listPreview = computed(() => {
|
const listPreview = computed(() => {
|
||||||
if (props.operator === 'in' && localValue.value) {
|
if (
|
||||||
|
props.operator === IotRuleSceneTriggerConditionParameterOperatorEnum.IN.value &&
|
||||||
|
localValue.value
|
||||||
|
) {
|
||||||
return localValue.value
|
return localValue.value
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((item) => item.trim())
|
.map((item) => item.trim())
|
||||||
|
|
@ -187,10 +179,7 @@ const listPreview = computed(() => {
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/** 判断是否为数字类型 */
|
||||||
* 判断是否为数字类型
|
|
||||||
* @returns 是否为数字类型
|
|
||||||
*/
|
|
||||||
const isNumericType = () => {
|
const isNumericType = () => {
|
||||||
return [
|
return [
|
||||||
IoTDataSpecsDataTypeEnum.INT,
|
IoTDataSpecsDataTypeEnum.INT,
|
||||||
|
|
@ -199,10 +188,7 @@ const isNumericType = () => {
|
||||||
].includes((props.propertyType || '') as any)
|
].includes((props.propertyType || '') as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取输入框类型 */
|
||||||
* 获取输入框类型
|
|
||||||
* @returns 输入框类型
|
|
||||||
*/
|
|
||||||
const getInputType = () => {
|
const getInputType = () => {
|
||||||
switch (props.propertyType) {
|
switch (props.propertyType) {
|
||||||
case IoTDataSpecsDataTypeEnum.INT:
|
case IoTDataSpecsDataTypeEnum.INT:
|
||||||
|
|
@ -214,10 +200,7 @@ const getInputType = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取占位符文本 */
|
||||||
* 获取占位符文本
|
|
||||||
* @returns 占位符文本
|
|
||||||
*/
|
|
||||||
const getPlaceholder = () => {
|
const getPlaceholder = () => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
[IoTDataSpecsDataTypeEnum.TEXT]: '请输入字符串',
|
[IoTDataSpecsDataTypeEnum.TEXT]: '请输入字符串',
|
||||||
|
|
@ -230,48 +213,27 @@ const getPlaceholder = () => {
|
||||||
return typeMap[props.propertyType || ''] || '请输入值'
|
return typeMap[props.propertyType || ''] || '请输入值'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取数字精度 */
|
||||||
* 获取数字精度
|
|
||||||
* @returns 数字精度
|
|
||||||
*/
|
|
||||||
const getPrecision = () => {
|
const getPrecision = () => {
|
||||||
return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 0 : 2
|
return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 0 : 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取数字步长 */
|
||||||
* 获取数字步长
|
|
||||||
* @returns 数字步长
|
|
||||||
*/
|
|
||||||
const getStep = () => {
|
const getStep = () => {
|
||||||
return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 1 : 0.1
|
return props.propertyType === IoTDataSpecsDataTypeEnum.INT ? 1 : 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取最小值 */
|
||||||
* 获取最小值
|
|
||||||
* @returns 最小值
|
|
||||||
*/
|
|
||||||
const getMin = () => {
|
const getMin = () => {
|
||||||
return props.propertyConfig?.min || undefined
|
return props.propertyConfig?.min || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取最大值 */
|
||||||
* 获取最大值
|
|
||||||
* @returns 最大值
|
|
||||||
*/
|
|
||||||
const getMax = () => {
|
const getMax = () => {
|
||||||
return props.propertyConfig?.max || undefined
|
return props.propertyConfig?.max || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 处理范围变化事件 */
|
||||||
* 处理值变化事件
|
|
||||||
*/
|
|
||||||
const handleChange = () => {
|
|
||||||
// 值变化处理
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理范围变化事件
|
|
||||||
*/
|
|
||||||
const handleRangeChange = () => {
|
const handleRangeChange = () => {
|
||||||
if (rangeStart.value && rangeEnd.value) {
|
if (rangeStart.value && rangeEnd.value) {
|
||||||
localValue.value = `${rangeStart.value},${rangeEnd.value}`
|
localValue.value = `${rangeStart.value},${rangeEnd.value}`
|
||||||
|
|
@ -280,23 +242,17 @@ const handleRangeChange = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 处理日期变化事件 */
|
||||||
* 处理日期变化事件
|
|
||||||
* @param value 日期值
|
|
||||||
*/
|
|
||||||
const handleDateChange = (value: string) => {
|
const handleDateChange = (value: string) => {
|
||||||
localValue.value = value || ''
|
localValue.value = value || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 处理数字变化事件 */
|
||||||
* 处理数字变化事件
|
|
||||||
* @param value 数字值
|
|
||||||
*/
|
|
||||||
const handleNumberChange = (value: number | undefined) => {
|
const handleNumberChange = (value: number | undefined) => {
|
||||||
localValue.value = value?.toString() || ''
|
localValue.value = value?.toString() || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听操作符变化
|
/** 监听操作符变化 */
|
||||||
watch(
|
watch(
|
||||||
() => props.operator,
|
() => props.operator,
|
||||||
() => {
|
() => {
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
<el-tag size="small" type="warning">自动执行</el-tag>
|
<el-tag size="small" type="warning">自动执行</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
|
<div class="text-12px text-[var(--el-text-color-secondary)] leading-relaxed">
|
||||||
当触发条件满足时,系统将自动发送告警通知,无需额外配置。
|
当触发条件满足时,系统将自动发送告警通知,可在菜单 [告警中心 -> 告警配置] 管理。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@
|
||||||
<Icon icon="ep:document" />
|
<Icon icon="ep:document" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-24px font-600 text-[#303133] leading-none">{{
|
<div class="text-24px font-600 text-[#303133] leading-none">
|
||||||
statistics.total
|
{{ statistics.total }}
|
||||||
}}</div>
|
</div>
|
||||||
<div class="text-14px text-[#909399] mt-4px">总规则数</div>
|
<div class="text-14px text-[#909399] mt-4px">总规则数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,9 +99,9 @@
|
||||||
<Icon icon="ep:check" />
|
<Icon icon="ep:check" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-24px font-600 text-[#303133] leading-none">{{
|
<div class="text-24px font-600 text-[#303133] leading-none">
|
||||||
statistics.enabled
|
{{ statistics.enabled }}
|
||||||
}}</div>
|
</div>
|
||||||
<div class="text-14px text-[#909399] mt-4px">启用规则</div>
|
<div class="text-14px text-[#909399] mt-4px">启用规则</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -119,9 +119,9 @@
|
||||||
<Icon icon="ep:close" />
|
<Icon icon="ep:close" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-24px font-600 text-[#303133] leading-none">{{
|
<div class="text-24px font-600 text-[#303133] leading-none">
|
||||||
statistics.disabled
|
{{ statistics.disabled }}
|
||||||
}}</div>
|
</div>
|
||||||
<div class="text-14px text-[#909399] mt-4px">禁用规则</div>
|
<div class="text-14px text-[#909399] mt-4px">禁用规则</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,9 +139,9 @@
|
||||||
<Icon icon="ep:timer" />
|
<Icon icon="ep:timer" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-24px font-600 text-[#303133] leading-none">{{
|
<div class="text-24px font-600 text-[#303133] leading-none">
|
||||||
statistics.timerRules
|
{{ statistics.timerRules }}
|
||||||
}}</div>
|
</div>
|
||||||
<div class="text-14px text-[#909399] mt-4px">定时规则</div>
|
<div class="text-14px text-[#909399] mt-4px">定时规则</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,7 +214,7 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="210" fixed="right">
|
<el-table-column label="操作" width="210" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="flex gap-8px">
|
<div>
|
||||||
<el-button type="primary" link @click="handleEdit(row)">
|
<el-button type="primary" link @click="handleEdit(row)">
|
||||||
<Icon icon="ep:edit" />
|
<Icon icon="ep:edit" />
|
||||||
编辑
|
编辑
|
||||||
|
|
@ -293,7 +293,6 @@ const statistics = ref({
|
||||||
total: 0,
|
total: 0,
|
||||||
enabled: 0,
|
enabled: 0,
|
||||||
disabled: 0,
|
disabled: 0,
|
||||||
triggered: 0, // 已触发的规则数量 (暂时使用启用状态的规则数量)
|
|
||||||
timerRules: 0 // 定时规则数量
|
timerRules: 0 // 定时规则数量
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -371,7 +370,6 @@ const updateStatistics = () => {
|
||||||
total: list.value.length,
|
total: list.value.length,
|
||||||
enabled: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
|
enabled: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
|
||||||
disabled: list.value.filter((item) => item.status === CommonStatusEnum.DISABLE).length,
|
disabled: list.value.filter((item) => item.status === CommonStatusEnum.DISABLE).length,
|
||||||
triggered: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
|
|
||||||
timerRules: list.value.filter((item) => hasTimerTrigger(item)).length
|
timerRules: list.value.filter((item) => hasTimerTrigger(item)).length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -459,6 +459,11 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
getDetail()
|
getDetail()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 销毁 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearQueryInterval()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@
|
||||||
<el-form-item label="创建时间" prop="createTime">
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.createTime"
|
v-model="queryParams.createTime"
|
||||||
style="width: 240px"
|
|
||||||
type="daterange"
|
type="daterange"
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
|
|
@ -122,7 +121,7 @@
|
||||||
label="模板内容"
|
label="模板内容"
|
||||||
align="center"
|
align="center"
|
||||||
prop="content"
|
prop="content"
|
||||||
width="200"
|
min-width="200"
|
||||||
:show-overflow-tooltip="true"
|
:show-overflow-tooltip="true"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="邮箱账号" align="center" prop="accountId" width="200">
|
<el-table-column label="邮箱账号" align="center" prop="accountId" width="200">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue