This commit is contained in:
吴红兵
2026-03-07 12:35:45 +08:00
parent 271710e870
commit b997b3ba48
423 changed files with 79612 additions and 91574 deletions

View File

@@ -104,9 +104,7 @@
</template> </template>
</el-upload> </el-upload>
</div> </div>
<div v-if="!uploadedImageBase64" class="text-xs text-red-500 mt-1"> <div v-if="!uploadedImageBase64" class="text-xs text-red-500 mt-1">请上传参考图片</div>
请上传参考图片
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -380,7 +378,7 @@ const generateImage = async () => {
ElMessage.warning('请输入提示词!'); ElMessage.warning('请输入提示词!');
return; return;
} }
if (!uploadedImageBase64.value) { if (!uploadedImageBase64.value) {
ElMessage.warning('请上传参考图片!'); ElMessage.warning('请上传参考图片!');
return; return;

View File

@@ -12,9 +12,9 @@
<!-- Right chat window --> <!-- Right chat window -->
<div class="flex-1" :class="{ 'w-full': !isDirectChat, 'flex-1': isDirectChat }"> <div class="flex-1" :class="{ 'w-full': !isDirectChat, 'flex-1': isDirectChat }">
<chat-window <chat-window
ref="chatWindowRef" ref="chatWindowRef"
:knowledge-id="selectedKnowledgeId" :knowledge-id="selectedKnowledgeId"
:knowledge-data="selectedKnowledge" :knowledge-data="selectedKnowledge"
:flow-id="props.flowId || (route.query.flowId as string) || ''" :flow-id="props.flowId || (route.query.flowId as string) || ''"
/> />
@@ -113,7 +113,7 @@ onBeforeMount(() => {
} }
} else { } else {
selectedKnowledgeId.value = datasetId; selectedKnowledgeId.value = datasetId;
selectedKnowledge.value = {id: datasetId}; selectedKnowledge.value = { id: datasetId };
} }
} }
}); });

View File

@@ -98,10 +98,7 @@ export const parseWelcomeMessage = (welcomeMsg?: string): PrologueItem[] => {
* @param {PrologueItem[]} initialPrologueList - 初始的 prologue 列表 * @param {PrologueItem[]} initialPrologueList - 初始的 prologue 列表
* @returns {Promise<PrologueItem[]>} 处理后的 prologue items * @returns {Promise<PrologueItem[]>} 处理后的 prologue items
*/ */
export const processPrologueItems = async ( export const processPrologueItems = async (selectedKnowledge: Dataset, initialPrologueList: PrologueItem[]): Promise<PrologueItem[]> => {
selectedKnowledge: Dataset,
initialPrologueList: PrologueItem[]
): Promise<PrologueItem[]> => {
// 如果存在 mcpId从后端获取 MCP 元数据 // 如果存在 mcpId从后端获取 MCP 元数据
if (selectedKnowledge?.mcpId) { if (selectedKnowledge?.mcpId) {
const { data } = await getObj(selectedKnowledge.mcpId); const { data } = await getObj(selectedKnowledge.mcpId);
@@ -143,21 +140,21 @@ const token = computed(() => {
export const generateConversationKey = (knowledgeId: string, notime?: boolean, mcpId?: string, dataId?: string) => { export const generateConversationKey = (knowledgeId: string, notime?: boolean, mcpId?: string, dataId?: string) => {
// 构建基础key // 构建基础key
let key = `chat-${knowledgeId}-${useUserInfo().userInfos.user.userId}-${token.value}`; let key = `chat-${knowledgeId}-${useUserInfo().userInfos.user.userId}-${token.value}`;
// 如果有mcpId添加到key中 // 如果有mcpId添加到key中
if (mcpId) { if (mcpId) {
key += `-mcp-${mcpId}`; key += `-mcp-${mcpId}`;
} }
// 如果有dataId添加到key中 // 如果有dataId添加到key中
if (dataId) { if (dataId) {
key += `-data-${dataId}`; key += `-data-${dataId}`;
} }
// 如果需要时间戳添加到key末尾 // 如果需要时间戳添加到key末尾
if (!notime) { if (!notime) {
key += `-${Date.now()}`; key += `-${Date.now()}`;
} }
return key; return key;
}; };

View File

@@ -82,9 +82,16 @@
v-if="!selectRow.recordId" v-if="!selectRow.recordId"
class="flex flex-col items-center justify-center h-full p-8 bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm" class="flex flex-col items-center justify-center h-full p-8 bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm"
> >
<div class="flex items-center justify-center w-16 h-16 mb-6 border bg-gradient-to-br from-indigo-50 to-blue-50 rounded-2xl border-indigo-100/50"> <div
class="flex items-center justify-center w-16 h-16 mb-6 border bg-gradient-to-br from-indigo-50 to-blue-50 rounded-2xl border-indigo-100/50"
>
<svg class="w-8 h-8 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-8 h-8 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> <path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg> </svg>
</div> </div>
<div class="text-center"> <div class="text-center">
@@ -95,7 +102,10 @@
<template v-else> <template v-else>
<div class="h-full overflow-hidden bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm"> <div class="h-full overflow-hidden bg-white border shadow-lg rounded-2xl border-slate-200/60 shadow-slate-100/50 backdrop-blur-sm">
<!-- 顶部标注切换区域 --> <!-- 顶部标注切换区域 -->
<div class="sticky top-0 z-20 flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-500/5 to-indigo-500/5 backdrop-blur-md border-blue-100/50" v-if="selectRow.llmFlag === '1'"> <div
class="sticky top-0 z-20 flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-500/5 to-indigo-500/5 backdrop-blur-md border-blue-100/50"
v-if="selectRow.llmFlag === '1'"
>
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<div class="relative"> <div class="relative">
<div class="w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 animate-pulse"></div> <div class="w-3 h-3 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 animate-pulse"></div>
@@ -105,10 +115,10 @@
</div> </div>
<div class="flex items-center px-4 py-2 space-x-4 border rounded-full bg-white/60 border-blue-100/50 backdrop-blur-sm"> <div class="flex items-center px-4 py-2 space-x-4 border rounded-full bg-white/60 border-blue-100/50 backdrop-blur-sm">
<label class="text-sm font-medium cursor-pointer select-none text-slate-700">标注为正确答案</label> <label class="text-sm font-medium cursor-pointer select-none text-slate-700">标注为正确答案</label>
<el-switch <el-switch
v-model="selectRow.standardFlag" v-model="selectRow.standardFlag"
@change="editHandle" @change="editHandle"
:active-value="'1'" :active-value="'1'"
:inactive-value="'0'" :inactive-value="'0'"
class="scale-110 drop-shadow-sm" class="scale-110 drop-shadow-sm"
></el-switch> ></el-switch>
@@ -121,20 +131,31 @@
<!-- 用户提问区域 --> <!-- 用户提问区域 -->
<div class="relative group"> <div class="relative group">
<div v-if="selectRow.llmFlag === '2'" class="relative"> <div v-if="selectRow.llmFlag === '2'" class="relative">
<div class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50"> <div
class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50"
>
<div class="leading-relaxed prose-sm prose max-w-none text-slate-700" v-html="matchResult" @click="handleChildClick" /> <div class="leading-relaxed prose-sm prose max-w-none text-slate-700" v-html="matchResult" @click="handleChildClick" />
</div> </div>
</div> </div>
<div v-else class="relative"> <div v-else class="relative">
<div class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-blue-50/30 to-indigo-50/30 rounded-2xl border-blue-200/50 hover:shadow-md hover:border-blue-300/50 hover:bg-gradient-to-br hover:from-blue-50/50 hover:to-indigo-50/50"> <div
class="p-6 transition-all duration-300 border shadow-sm bg-gradient-to-br from-blue-50/30 to-indigo-50/30 rounded-2xl border-blue-200/50 hover:shadow-md hover:border-blue-300/50 hover:bg-gradient-to-br hover:from-blue-50/50 hover:to-indigo-50/50"
>
<div class="leading-relaxed prose-sm prose max-w-none text-slate-800 font-medium"> <div class="leading-relaxed prose-sm prose max-w-none text-slate-800 font-medium">
{{ selectRow.questionText }} {{ selectRow.questionText }}
</div> </div>
</div> </div>
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<div class="flex items-center px-2 py-1 text-xs font-medium text-blue-600 bg-blue-50/80 rounded-full border border-blue-200/50 backdrop-blur-sm"> <div
class="flex items-center px-2 py-1 text-xs font-medium text-blue-600 bg-blue-50/80 rounded-full border border-blue-200/50 backdrop-blur-sm"
>
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> <path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg> </svg>
问题 问题
</div> </div>
@@ -146,9 +167,16 @@
<div class="relative group"> <div class="relative group">
<div class="flex items-center mb-5 space-x-3"> <div class="flex items-center mb-5 space-x-3">
<div class="relative"> <div class="relative">
<div class="flex items-center justify-center w-10 h-10 border shadow-sm bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl border-blue-100/50"> <div
class="flex items-center justify-center w-10 h-10 border shadow-sm bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl border-blue-100/50"
>
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path> <path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
></path>
</svg> </svg>
</div> </div>
<div class="absolute w-3 h-3 border-2 border-white rounded-full shadow-sm -top-1 -right-1 bg-blue-400"></div> <div class="absolute w-3 h-3 border-2 border-white rounded-full shadow-sm -top-1 -right-1 bg-blue-400"></div>
@@ -157,16 +185,25 @@
<div> <div>
<h3 class="text-lg font-bold text-slate-800">AI 智能回答</h3> <h3 class="text-lg font-bold text-slate-800">AI 智能回答</h3>
</div> </div>
<div class="flex items-center px-3 py-1 border rounded-full bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100/50"> <div
class="flex items-center px-3 py-1 border rounded-full bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100/50"
>
<svg class="w-3 h-3 text-blue-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 text-blue-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> <path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg> </svg>
<span class="text-xs font-medium text-blue-600">支持Markdown</span> <span class="text-xs font-medium text-blue-600">支持Markdown</span>
</div> </div>
</div> </div>
</div> </div>
<div class="overflow-hidden transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50"> <div
class="overflow-hidden transition-all duration-300 border shadow-sm bg-gradient-to-br from-white to-slate-50/30 rounded-2xl border-slate-200/50 hover:shadow-md hover:border-slate-300/50"
>
<ai-editor <ai-editor
v-model="selectRow.answerText" v-model="selectRow.answerText"
output="text" output="text"

View File

@@ -235,12 +235,7 @@
可见范围 可见范围
<tip content="选择可以访问此知识库的用户,如果不选择则全部的用户可访问" /> <tip content="选择可以访问此知识库的用户,如果不选择则全部的用户可访问" />
</template> </template>
<org-selector <org-selector v-model="form.visibleUsers" :type="'user'" :multiple="true" :selectSelf="true" />
v-model="form.visibleUsers"
:type="'user'"
:multiple="true"
:selectSelf="true"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>

View File

@@ -1,215 +1,211 @@
<template> <template>
<el-dialog <el-dialog v-model="dialogVisible" title="选择挂载方式" width="50%" :modal="false">
v-model="dialogVisible" <div>
title="选择挂载方式" <el-card size="mini">
width="50%" <el-row :gutter="10" justify="center">
:modal="false" <el-col :span="8">
> <div :class="selectedMethod === 1 ? 'img-selected' : 'img-unselect'" @click="selectImg(1)">
<div> <div class="css-1awpln7">
<el-card size="mini"> <div class="css-0">
<el-row :gutter="10" justify="center"> <img alt="" src="/@/assets/ai/link.svg" class="chakra-image css-0" />
<el-col :span="8"> </div>
<div :class="selectedMethod === 1 ? 'img-selected' : 'img-unselect'" @click="selectImg(1)"> </div>
<div class="css-1awpln7"> </div>
<div class="css-0"> </el-col>
<img alt="" src="/@/assets/ai/link.svg" class="chakra-image css-0"> <el-col :span="8">
</div> <div :class="selectedMethod === 2 ? 'img-selected' : 'img-unselect'" @click="selectImg(2)">
</div> <div class="css-1awpln7">
</div> <div class="css-0">
</el-col> <img alt="" src="/@/assets/ai/iframe.svg" class="chakra-image css-0" />
<el-col :span="8"> </div>
<div :class="selectedMethod === 2 ? 'img-selected' : 'img-unselect'" @click="selectImg(2)"> </div>
<div class="css-1awpln7"> </div>
<div class="css-0"> </el-col>
<img alt="" src="/@/assets/ai/iframe.svg" class="chakra-image css-0"> <el-col :span="8">
</div> <div :class="selectedMethod === 3 ? 'img-selected' : 'img-unselect'" @click="selectImg(3)">
</div> <div class="css-1awpln7">
</div> <div class="css-0">
</el-col> <img alt="" src="/@/assets/ai/script.svg" class="chakra-image css-0" />
<el-col :span="8"> </div>
<div :class="selectedMethod === 3 ? 'img-selected' : 'img-unselect'" @click="selectImg(3)"> </div>
<div class="css-1awpln7"> </div>
<div class="css-0"> </el-col>
<img alt="" src="/@/assets/ai/script.svg" class="chakra-image css-0"> </el-row>
</div> </el-card>
</div> </div>
</div> <div>
</el-col> <el-card size="mini" body-class="card-class">
</el-row> <template #header>
</el-card> <div class="card-header">
</div> <div v-if="selectedMethod === 1">
<div> <span>将下面链接复制到浏览器打开</span>
<el-card size="mini" body-class="card-class"> <span>
<template #header> <el-button style="float: right" class="ml-2" type="primary" @click="openText(url())">打开</el-button>
<div class="card-header"> <el-button style="float: right" type="primary" @click="copyText(url())">复制</el-button>
<div v-if="selectedMethod === 1"> </span>
<span>将下面链接复制到浏览器打开</span> </div>
<span> <div v-if="selectedMethod === 2">
<el-button style="float: right" class="ml-2" type="primary" @click="openText(url())">打开</el-button> <span>复制下面 Iframe 加入到你的网站中</span>
<el-button style="float: right" type="primary" @click="copyText(url())">复制</el-button> <span>
</span> <el-button style="float: right" type="primary" @click="copyText(iframe())">复制</el-button>
</div> </span>
<div v-if="selectedMethod === 2"> </div>
<span>复制下面 Iframe 加入到你的网站中</span> <div v-if="selectedMethod === 3">
<span> <span>将下面代码加入到你的网站中</span>
<el-button style="float: right" type="primary" @click="copyText(iframe())">复制</el-button> <span>
</span> <el-button style="float: right" type="primary" @click="copyText(script())">复制</el-button>
</div> </span>
<div v-if="selectedMethod === 3"> </div>
<span>将下面代码加入到你的网站中</span> </div>
<span> </template>
<el-button style="float: right" type="primary" @click="copyText(script())">复制</el-button> <span v-if="selectedMethod === 1">
</span> <div class="mockup-code">
</div> <pre data-prefix="$"><code> {{ url() }}</code></pre>
</div> </div>
</template> </span>
<span v-if="selectedMethod === 1"> <span v-if="selectedMethod === 2">
<div class="mockup-code"> <div class="mockup-code">
<pre data-prefix="$"><code> {{ url() }}</code></pre> <pre data-prefix="$"><code> {{ iframe() }}</code></pre>
</div> </div>
</span> </span>
<span v-if="selectedMethod === 2"> <span v-if="selectedMethod === 3">
<div class="mockup-code"> <div class="mockup-code">
<pre data-prefix="$"><code> {{ iframe() }}</code></pre> <pre data-prefix="$"><code> {{ script() }}</code></pre>
</div> </div>
</span> </span>
<span v-if="selectedMethod === 3"> </el-card>
<div class="mockup-code"> </div>
<pre data-prefix="$"><code> {{ script() }}</code></pre> </el-dialog>
</div>
</span>
</el-card>
</div>
</el-dialog>
</template> </template>
<script setup lang="ts" name="AiDatasetDialog"> <script setup lang="ts" name="AiDatasetDialog">
import commonFunction from "/@/utils/commonFunction"; import commonFunction from '/@/utils/commonFunction';
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
const {copyText} = commonFunction(); const { copyText } = commonFunction();
const left_right = [ const left_right = [
{ {
label: '左侧', label: '左侧',
value: 'left', value: 'left',
}, },
{ {
label: '右侧', label: '右侧',
value: 'right', value: 'right',
} },
] ];
const top_bottom = [ const top_bottom = [
{ {
label: '顶部', label: '顶部',
value: 'top', value: 'top',
}, },
{ {
label: '底部', label: '底部',
value: 'bottom', value: 'bottom',
} },
] ];
const dialogVisible = ref(false) const dialogVisible = ref(false);
const selectedDatasetId = ref() const selectedDatasetId = ref();
const selectedMethod = ref(1) const selectedMethod = ref(1);
const data_btn_x = ref(16) const data_btn_x = ref(16);
const data_btn_y = ref(16) const data_btn_y = ref(16);
const data_stream = ref(true) const data_stream = ref(true);
const data_direction_x = ref('right') const data_direction_x = ref('right');
const data_direction_y = ref('bottom') const data_direction_y = ref('bottom');
const openDialog = (datasetid: any) => { const openDialog = (datasetid: any) => {
dialogVisible.value = true dialogVisible.value = true;
selectedDatasetId.value = datasetid selectedDatasetId.value = datasetid;
} };
const currentHostname = window.location.origin; const currentHostname = window.location.origin;
const openImageBase64 = ref("") const openImageBase64 = ref('');
const closeImageBase64 = ref("") const closeImageBase64 = ref('');
function handleOpenIconBeforeUpload(file: Blob) { function handleOpenIconBeforeUpload(file: Blob) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onload = e => { reader.onload = (e) => {
// 在这里处理 Base64 数据 // 在这里处理 Base64 数据
openImageBase64.value = e.target.result openImageBase64.value = e.target.result;
}; };
return false; return false;
} }
function handleCloseIconBeforeUpload(file: Blob) { function handleCloseIconBeforeUpload(file: Blob) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onload = e => { reader.onload = (e) => {
// 在这里处理 Base64 数据 // 在这里处理 Base64 数据
closeImageBase64.value = e.target.result closeImageBase64.value = e.target.result;
}; };
return false; return false;
} }
const isMicro = import.meta.env.VITE_IS_MICRO === 'true' ? 1 : 0;
const isMicro = import.meta.env.VITE_IS_MICRO === 'true' ? 1 : 0
const script = () => { const script = () => {
return `<script id="chatbot-iframe" src="${currentHostname}/bot/embed.min.js?t=${Date.now()}" data-bot-src="${currentHostname}/bot/index.html#/${isMicro}/${selectedDatasetId.value}/chat" async defer><\/script>` return `<script id="chatbot-iframe" src="${currentHostname}/bot/embed.min.js?t=${Date.now()}" data-bot-src="${currentHostname}/bot/index.html#/${isMicro}/${
} selectedDatasetId.value
}/chat" async defer><\/script>`;
};
const iframe = () => { const iframe = () => {
return `<iframe src="${currentHostname}/bot/bot/index.html#/${isMicro}/${selectedDatasetId.value}/chat" style="width: 100%; height: 100%;" frameborder="0" allow="microphone"/>` return `<iframe src="${currentHostname}/bot/bot/index.html#/${isMicro}/${selectedDatasetId.value}/chat" style="width: 100%; height: 100%;" frameborder="0" allow="microphone"/>`;
} };
const url = () => { const url = () => {
return `${currentHostname}/bot/index.html#/${isMicro}/${selectedDatasetId.value}/chat` return `${currentHostname}/bot/index.html#/${isMicro}/${selectedDatasetId.value}/chat`;
} };
const selectImg = (index: number) => { const selectImg = (index: number) => {
selectedMethod.value = index selectedMethod.value = index;
} };
const openText = (url: string) => { const openText = (url: string) => {
window.open(url) window.open(url);
} };
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
openDialog openDialog,
}); });
</script> </script>
<style scoped> <style scoped>
.img-unselect { .img-unselect {
display: flex; display: flex;
-webkit-box-align: center; -webkit-box-align: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border: 1.5px solid rgb(232, 235, 240); border: 1.5px solid rgb(232, 235, 240);
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
background: #fbfbfc; background: #fbfbfc;
padding: 0px !important; padding: 0px !important;
} }
.img-unselect:hover { .img-unselect:hover {
border-color: #1dbcd8; border-color: #1dbcd8;
} }
.img-selected { .img-selected {
display: flex; display: flex;
-webkit-box-align: center; -webkit-box-align: center;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border-style: solid; border-style: solid;
border-image: initial; border-image: initial;
border-width: 1.5px; border-width: 1.5px;
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
border-color: #1dbcd8; border-color: #1dbcd8;
background: #f0f4ff; background: #f0f4ff;
padding: 0px !important; padding: 0px !important;
} }
.card-class { .card-class {
background-color: #F4F4F7; background-color: #f4f4f7;
} }
</style> </style>

View File

@@ -1,5 +1,13 @@
<template> <template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :width="800" :close-on-click-modal="false" draggable :destroy-on-close="true" class="dark:bg-gray-800"> <el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable
:destroy-on-close="true"
class="dark:bg-gray-800"
>
<el-form <el-form
ref="dataFormRef" ref="dataFormRef"
:model="form" :model="form"

View File

@@ -127,12 +127,9 @@
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" /> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 文档查看抽屉 --> <!-- 文档查看抽屉 -->
<document-drawer <document-drawer v-model="documentDrawerVisible" :document-id="selectedDocumentId" />
v-model="documentDrawerVisible"
:document-id="selectedDocumentId"
/>
</div> </div>
</template> </template>
@@ -274,7 +271,7 @@ const viewDocument = (document: any) => {
useMessage().warning('只有切片成功的文档才能查看'); useMessage().warning('只有切片成功的文档才能查看');
return; return;
} }
selectedDocumentId.value = document.id; selectedDocumentId.value = document.id;
documentDrawerVisible.value = true; documentDrawerVisible.value = true;
}; };

View File

@@ -9,7 +9,6 @@
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,

View File

@@ -1,34 +1,30 @@
<template> <template>
<el-col class="mb20"> <el-col class="mb20">
<el-form-item label="资料" prop="files"> <el-form-item label="资料" prop="files">
<upload-file <upload-file :limit="1" @change="handleFileChange" :fileType="['xlsx']" />
:limit="1" <a class="link link-primary" @click="downloadTemplate">Q&A Excel 模板下载</a>
@change="handleFileChange" </el-form-item>
:fileType="['xlsx']" </el-col>
/>
<a class="link link-primary" @click="downloadTemplate">Q&A Excel 模板下载</a>
</el-form-item>
</el-col>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from 'vue'; import { PropType } from 'vue';
import { downBlobFile } from "/@/utils/other"; import { downBlobFile } from '/@/utils/other';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object as PropType<any>, type: Object as PropType<any>,
required: true required: true,
} },
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const handleFileChange = (fileNames: string, fileList: any[]) => { const handleFileChange = (fileNames: string, fileList: any[]) => {
emit('update:modelValue', fileList); emit('update:modelValue', fileList);
} };
const downloadTemplate = () => { const downloadTemplate = () => {
downBlobFile('/admin/sys-file/local/file/qa.xlsx', {}, 'Q&A.xlsx'); downBlobFile('/admin/sys-file/local/file/qa.xlsx', {}, 'Q&A.xlsx');
}; };
</script> </script>

View File

@@ -6,12 +6,7 @@
</el-col> </el-col>
<el-col class="mb20"> <el-col class="mb20">
<el-form-item label="内容" prop="content"> <el-form-item label="内容" prop="content">
<ai-editor <ai-editor v-model="modelValue.content" output="text" placeholder="选择输入文本,即可调用 AI 辅助功能" :minHeight="400" />
v-model="modelValue.content"
output="text"
placeholder="选择输入文本,即可调用 AI 辅助功能"
:minHeight="400"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</template> </template>

View File

@@ -242,7 +242,7 @@ const onSubmit = async () => {
loading.value = true; loading.value = true;
// 修复TypeScript错误正确处理apiKey的类型 // 修复TypeScript错误正确处理apiKey的类型
const submitForm = { ...form }; const submitForm = { ...form };
// 处理 pgvector 类型的特殊逻辑 // 处理 pgvector 类型的特殊逻辑
if (form.storeType === 'pgvector') { if (form.storeType === 'pgvector') {
// 将 pgvector 的配置信息存储到 extData 中 // 将 pgvector 的配置信息存储到 extData 中
@@ -253,7 +253,7 @@ const onSubmit = async () => {
dimension: form.pgDimension, dimension: form.pgDimension,
}; };
submitForm.extData = JSON.stringify(pgConfig); submitForm.extData = JSON.stringify(pgConfig);
// 清空不需要的字段 // 清空不需要的字段
submitForm.apiKey = ''; submitForm.apiKey = '';
} else { } else {
@@ -262,13 +262,13 @@ const onSubmit = async () => {
submitForm.apiKey = ''; submitForm.apiKey = '';
} }
} }
// 移除 pgvector 特有的临时字段 // 移除 pgvector 特有的临时字段
delete (submitForm as any).pgUsername; delete (submitForm as any).pgUsername;
delete (submitForm as any).pgPassword; delete (submitForm as any).pgPassword;
delete (submitForm as any).pgDatabase; delete (submitForm as any).pgDatabase;
delete (submitForm as any).pgDimension; delete (submitForm as any).pgDimension;
form.storeId ? await putObj(submitForm) : await addObj(submitForm); form.storeId ? await putObj(submitForm) : await addObj(submitForm);
useMessage().success(form.storeId ? '修改成功' : '添加成功'); useMessage().success(form.storeId ? '修改成功' : '添加成功');
visible.value = false; visible.value = false;
@@ -287,7 +287,7 @@ const getaiEmbedStoreData = (id: string) => {
getObj({ storeId: id }) getObj({ storeId: id })
.then((res: any) => { .then((res: any) => {
Object.assign(form, res.data); Object.assign(form, res.data);
// 如果是 pgvector 类型,解析 extData 中的配置 // 如果是 pgvector 类型,解析 extData 中的配置
if (form.storeType === 'pgvector' && form.extData) { if (form.storeType === 'pgvector' && form.extData) {
try { try {

View File

@@ -1,173 +1,173 @@
<template> <template>
<div v-if="visible" <div
class="context-menu" v-if="visible"
:style="{ class="context-menu"
position: 'fixed', :style="{
left: position.x + 'px', position: 'fixed',
top: position.y + 'px', left: position.x + 'px',
zIndex: 1000 top: position.y + 'px',
}"> zIndex: 1000,
<!-- <div class="menu-item" }"
>
<!-- <div class="menu-item"
@click="handleCommand('addNode')"> @click="handleCommand('addNode')">
<el-icon class="menu-icon"> <el-icon class="menu-icon">
<Plus /> <Plus />
</el-icon> </el-icon>
新增节点 新增节点
</div> --> </div> -->
<div class="menu-item" <div class="menu-item" @click="handleCommand('importDSL')">
@click="handleCommand('importDSL')"> <el-icon class="menu-icon">
<el-icon class="menu-icon"> <Upload />
<Upload /> </el-icon>
</el-icon> 导入DSL
导入DSL </div>
</div> <div class="menu-item" @click="handleCommand('exportDSL')">
<div class="menu-item" <el-icon class="menu-icon">
@click="handleCommand('exportDSL')"> <Download />
<el-icon class="menu-icon"> </el-icon>
<Download /> 导出DSL
</el-icon> </div>
导出DSL </div>
</div>
</div>
</template> </template>
<script> <script>
import { ref, defineComponent, onMounted, onBeforeUnmount } from 'vue' import { ref, defineComponent, onMounted, onBeforeUnmount } from 'vue';
import { Plus, Upload, Download } from '@element-plus/icons-vue' import { Plus, Upload, Download } from '@element-plus/icons-vue';
export default defineComponent({ export default defineComponent({
name: 'CanvasContextMenu', name: 'CanvasContextMenu',
components: { components: {
Plus, Plus,
Upload, Upload,
Download Download,
}, },
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
position: { position: {
type: Object, type: Object,
default: () => ({ x: 0, y: 0 }) default: () => ({ x: 0, y: 0 }),
} },
}, },
emits: ['update:visible', 'add-node', 'import-dsl', 'export-dsl'], emits: ['update:visible', 'add-node', 'import-dsl', 'export-dsl'],
setup (props, { emit }) { setup(props, { emit }) {
// 处理菜单命令 // 处理菜单命令
const handleCommand = (command) => { const handleCommand = (command) => {
switch (command) { switch (command) {
case 'addNode': case 'addNode':
emit('add') emit('add');
break break;
case 'importDSL': case 'importDSL':
emit('import') emit('import');
break break;
case 'exportDSL': case 'exportDSL':
emit('export') emit('export');
break break;
} }
emit('update:visible', false) emit('update:visible', false);
} };
// 点击外部关闭菜单的处理函数 // 点击外部关闭菜单的处理函数
const handleClickOutside = (e) => { const handleClickOutside = (e) => {
const contextMenu = document.querySelector('.context-menu') const contextMenu = document.querySelector('.context-menu');
if (contextMenu && !contextMenu.contains(e.target)) { if (contextMenu && !contextMenu.contains(e.target)) {
emit('update:visible', false) emit('update:visible', false);
} }
} };
// 组件挂载时添加点击事件监听 // 组件挂载时添加点击事件监听
onMounted(() => { onMounted(() => {
document.addEventListener('click', handleClickOutside) document.addEventListener('click', handleClickOutside);
}) });
// 组件销毁前移除事件监听 // 组件销毁前移除事件监听
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside) document.removeEventListener('click', handleClickOutside);
}) });
return { return {
handleCommand, handleCommand,
} };
} },
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.context-menu { .context-menu {
opacity: 0.9; opacity: 0.9;
background: #ffffff; background: #ffffff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.05);
padding: 6px 0; padding: 6px 0;
min-width: 180px; min-width: 180px;
border: 1px solid rgba(0, 0, 0, 0.06); border: 1px solid rgba(0, 0, 0, 0.06);
animation: menuFadeIn 0.15s ease-out; animation: menuFadeIn 0.15s ease-out;
} }
.menu-item { .menu-item {
padding: 8px 16px; padding: 8px 16px;
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
color: rgb(71 84 103 / 1); color: rgb(71 84 103 / 1);
font-size: 13px; font-size: 13px;
white-space: nowrap; white-space: nowrap;
.menu-icon { .menu-icon {
margin-right: 8px; margin-right: 8px;
font-size: 16px; font-size: 16px;
} }
&:hover { &:hover {
background-color: #f8fafc; background-color: #f8fafc;
transform: translateX(2px); transform: translateX(2px);
} }
} }
.node-types { .node-types {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 16px; gap: 16px;
padding: 16px; padding: 16px;
.el-radio { .el-radio {
margin-right: 20px; margin-right: 20px;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
@keyframes menuFadeIn { @keyframes menuFadeIn {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.95); transform: scale(0.95);
} }
to { to {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
} }
} }
:deep(.el-dialog) { :deep(.el-dialog) {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
.el-dialog__header { .el-dialog__header {
margin: 0; margin: 0;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
} }
.el-dialog__body { .el-dialog__body {
padding: 20px; padding: 20px;
} }
.el-dialog__footer { .el-dialog__footer {
padding: 16px 20px; padding: 16px 20px;
border-top: 1px solid #e5e7eb; border-top: 1px solid #e5e7eb;
} }
} }
</style> </style>

View File

@@ -1,451 +1,442 @@
<template> <template>
<div class="check-list-panel"> <div class="check-list-panel">
<div class="preview-content"> <div class="preview-content">
<div class="preview-header"> <div class="preview-header">
<div class="header-title"> <div class="header-title">
<span>检查清单({{ validation.errors.length }})</span> <span>检查清单({{ validation.errors.length }})</span>
</div> </div>
<el-button <el-button class="close-btn" type="primary" link @click="$emit('close')">
class="close-btn" <el-icon><Close /></el-icon>
type="primary" </el-button>
link </div>
@click="$emit('close')" <div class="preview-body">
> <div class="check-list">
<el-icon><Close /></el-icon> <template v-if="validation.errors.length > 0">
</el-button> <div v-for="(error, index) in validation.errors" :key="index" class="check-item">
</div> <div class="item-icon warning">
<div class="preview-body"> <el-icon><Warning /></el-icon>
<div class="check-list"> </div>
<template v-if="validation.errors.length > 0"> <div class="item-content">
<div v-for="(error, index) in validation.errors" <div class="item-type">{{ getErrorType(error) }}</div>
:key="index" <div class="item-message">{{ getErrorMessage(error) }}</div>
class="check-item"> </div>
<div class="item-icon warning"> </div>
<el-icon><Warning /></el-icon> </template>
</div> <div v-else class="check-item success">
<div class="item-content"> <div class="item-icon success">
<div class="item-type">{{ getErrorType(error) }}</div> <el-icon><Select /></el-icon>
<div class="item-message">{{ getErrorMessage(error) }}</div> </div>
</div> <div class="item-content">
</div> <div class="item-type success">检测通过</div>
</template> <div class="item-message">工作流程检测已通过可以发布</div>
<div v-else </div>
class="check-item success"> </div>
<div class="item-icon success"> </div>
<el-icon><Select /></el-icon> </div>
</div> </div>
<div class="item-content"> </div>
<div class="item-type success">检测通过</div>
<div class="item-message">工作流程检测已通过可以发布</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { Warning, Close, Select } from '@element-plus/icons-vue' import { Warning, Close, Select } from '@element-plus/icons-vue';
export default { export default {
name: 'CheckListPanel', name: 'CheckListPanel',
inject: ['parent'], inject: ['parent'],
components: { components: {
Warning, Warning,
Close, Close,
Select Select,
}, },
props: { props: {
validation: { validation: {
type: Object, type: Object,
required: true required: true,
} },
}, },
emits: ['update:validation', 'close'], emits: ['update:validation', 'close'],
data () { data() {
return { return {
showCheckList: false showCheckList: false,
} };
}, },
watch: { watch: {
'parent.nodes': { 'parent.nodes': {
handler: 'checkChanges', handler: 'checkChanges',
deep: true, deep: true,
immediate: true immediate: true,
}, },
'parent.connections': { 'parent.connections': {
handler: 'checkChanges', handler: 'checkChanges',
deep: true, deep: true,
immediate: true immediate: true,
} },
}, },
methods: { methods: {
checkChanges () { checkChanges() {
const newValidation = this.validateWorkflow() const newValidation = this.validateWorkflow();
this.$emit('update:validation', newValidation) this.$emit('update:validation', newValidation);
}, },
validateWorkflow () { validateWorkflow() {
const validation = { const validation = {
isValid: true, isValid: true,
errors: [], errors: [],
warnings: [] warnings: [],
}; };
try { try {
if (!this.parent.nodes || this.parent.nodes.length === 0) { if (!this.parent.nodes || this.parent.nodes.length === 0) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '工作流中没有节点' message: '工作流中没有节点',
}); });
validation.isValid = false; validation.isValid = false;
return validation; return validation;
} }
const startNodes = this.parent.nodes.filter(node => node.type === 'start'); const startNodes = this.parent.nodes.filter((node) => node.type === 'start');
const endNodes = this.parent.nodes.filter(node => node.type === 'end'); const endNodes = this.parent.nodes.filter((node) => node.type === 'end');
if (startNodes.length === 0) { if (startNodes.length === 0) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '工作流缺少开始节点' message: '工作流缺少开始节点',
}); });
validation.isValid = false; validation.isValid = false;
} else if (startNodes.length > 1) { } else if (startNodes.length > 1) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '工作流只能有一个开始节点' message: '工作流只能有一个开始节点',
}); });
validation.isValid = false; validation.isValid = false;
} }
if (endNodes.length === 0) { if (endNodes.length === 0) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '工作流缺少结束节点' message: '工作流缺少结束节点',
}); });
validation.isValid = false; validation.isValid = false;
} }
const nodeConnections = new Map(); const nodeConnections = new Map();
this.parent.nodes.forEach(node => { this.parent.nodes.forEach((node) => {
nodeConnections.set(node.id, { nodeConnections.set(node.id, {
inbound: [], inbound: [],
outbound: [] outbound: [],
}); });
}); });
this.parent.connections.forEach(conn => { this.parent.connections.forEach((conn) => {
const sourceConn = nodeConnections.get(conn.sourceId); const sourceConn = nodeConnections.get(conn.sourceId);
const targetConn = nodeConnections.get(conn.targetId); const targetConn = nodeConnections.get(conn.targetId);
if (sourceConn) { if (sourceConn) {
sourceConn.outbound.push(conn.targetId); sourceConn.outbound.push(conn.targetId);
} }
if (targetConn) { if (targetConn) {
targetConn.inbound.push(conn.sourceId); targetConn.inbound.push(conn.sourceId);
} }
}); });
this.parent.nodes.forEach(node => { this.parent.nodes.forEach((node) => {
const nodeConn = nodeConnections.get(node.id); const nodeConn = nodeConnections.get(node.id);
if (node.type === 'start' && nodeConn.inbound.length > 0) { if (node.type === 'start' && nodeConn.inbound.length > 0) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '开始节点不能有入边连接', message: '开始节点不能有入边连接',
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
if (node.type === 'end' && nodeConn.outbound.length > 0) { if (node.type === 'end' && nodeConn.outbound.length > 0) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '结束节点不能有出边连接', message: '结束节点不能有出边连接',
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
if (nodeConn.inbound.length === 0 && nodeConn.outbound.length === 0 && if (nodeConn.inbound.length === 0 && nodeConn.outbound.length === 0 && node.type !== 'start' && node.type !== 'end') {
node.type !== 'start' && node.type !== 'end') { validation.warnings.push({
validation.warnings.push({ type: 'warning',
type: 'warning', message: '存在孤立节点',
message: '存在孤立节点', nodeId: node.id,
nodeId: node.id });
}); }
} });
});
this.parent.nodes.forEach(node => { this.parent.nodes.forEach((node) => {
// switch (node.type) { // switch (node.type) {
// case 'http': // case 'http':
// this.validateHttpNode(node, validation); // this.validateHttpNode(node, validation);
// break; // break;
// case 'code': // case 'code':
// this.validateCodeNode(node, validation); // this.validateCodeNode(node, validation);
// break; // break;
// } // }
}); });
if (this.hasCircularDependency()) { if (this.hasCircularDependency()) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '工作流中存在循环依赖' message: '工作流中存在循环依赖',
}); });
validation.isValid = false; validation.isValid = false;
} }
return validation; return validation;
} catch (error) { } catch (error) {
console.error('验证工作流时出错:', error); console.error('验证工作流时出错:', error);
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '验证工作流时出错: ' + error.message message: '验证工作流时出错: ' + error.message,
}); });
validation.isValid = false; validation.isValid = false;
return validation; return validation;
} }
}, },
validateHttpNode (node, validation) { validateHttpNode(node, validation) {
if (!node.url) { if (!node.url) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: 'HTTP节点缺少URL', message: 'HTTP节点缺少URL',
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
if (!node.method) { if (!node.method) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: 'HTTP节点缺少请求方法', message: 'HTTP节点缺少请求方法',
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
if (node.headers) { if (node.headers) {
node.headers.forEach((header, index) => { node.headers.forEach((header, index) => {
if (!header.name) { if (!header.name) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: `HTTP节点的第${index + 1}个请求头缺少名称`, message: `HTTP节点的第${index + 1}个请求头缺少名称`,
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
}); });
} }
if (node.method !== 'GET' && node.bodyParams) { if (node.method !== 'GET' && node.bodyParams) {
node.bodyParams.forEach((param, index) => { node.bodyParams.forEach((param, index) => {
if (!param.name) { if (!param.name) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: `HTTP节点的第${index + 1}个请求体参数缺少名称`, message: `HTTP节点的第${index + 1}个请求体参数缺少名称`,
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
}); });
} }
}, },
validateCodeNode (node, validation) { validateCodeNode(node, validation) {
if (!node.code) { if (!node.code) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: '代码节点缺少执行代码', message: '代码节点缺少执行代码',
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} else { } else {
try { try {
new Function(node.code); new Function(node.code);
} catch (error) { } catch (error) {
validation.errors.push({ validation.errors.push({
type: 'error', type: 'error',
message: `代码语法错误: ${error.message}`, message: `代码语法错误: ${error.message}`,
nodeId: node.id nodeId: node.id,
}); });
validation.isValid = false; validation.isValid = false;
} }
} }
}, },
hasCircularDependency () { hasCircularDependency() {
const visited = new Set(); const visited = new Set();
const recursionStack = new Set(); const recursionStack = new Set();
const dfs = (nodeId) => { const dfs = (nodeId) => {
visited.add(nodeId); visited.add(nodeId);
recursionStack.add(nodeId); recursionStack.add(nodeId);
const connections = this.parent.connections.filter(conn => conn.sourceId === nodeId); const connections = this.parent.connections.filter((conn) => conn.sourceId === nodeId);
for (const conn of connections) { for (const conn of connections) {
const targetId = conn.targetId; const targetId = conn.targetId;
if (!visited.has(targetId)) { if (!visited.has(targetId)) {
if (dfs(targetId)) { if (dfs(targetId)) {
return true; return true;
} }
} else if (recursionStack.has(targetId)) { } else if (recursionStack.has(targetId)) {
return true; return true;
} }
} }
recursionStack.delete(nodeId); recursionStack.delete(nodeId);
return false; return false;
}; };
for (const node of this.parent.nodes) { for (const node of this.parent.nodes) {
if (!visited.has(node.id)) { if (!visited.has(node.id)) {
if (dfs(node.id)) { if (dfs(node.id)) {
return true; return true;
} }
} }
} }
return false; return false;
}, },
getErrorType (error) { getErrorType(error) {
if (error.message.includes('缺少')) return '节点缺失' if (error.message.includes('缺少')) return '节点缺失';
if (error.message.includes('未连接')) return '连接错误' if (error.message.includes('未连接')) return '连接错误';
if (error.message.includes('循环')) return '循环依赖' if (error.message.includes('循环')) return '循环依赖';
return '错误' return '错误';
}, },
getErrorMessage (error) { getErrorMessage(error) {
let nodeName = '' let nodeName = '';
if (error.nodeId) { if (error.nodeId) {
const node = this.parent.nodes.find(n => n.id === error.nodeId) const node = this.parent.nodes.find((n) => n.id === error.nodeId);
if (node) { if (node) {
nodeName = node.name || `${node.type}节点` nodeName = node.name || `${node.type}节点`;
} }
} }
if (nodeName) { if (nodeName) {
return `${nodeName}: ${error.message}` return `${nodeName}: ${error.message}`;
} }
return error.message return error.message;
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.check-list-panel { .check-list-panel {
position: fixed; position: fixed;
top: 80px; top: 80px;
right: 20px; right: 20px;
z-index: 1; z-index: 1;
background: white; background: white;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
width: 400px; width: 400px;
} }
.preview-content { .preview-content {
position: relative; position: relative;
width: 100%; width: 100%;
} }
.preview-header { .preview-header {
padding: 12px; padding: 12px;
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.header-title { .header-title {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
span:first-child { span:first-child {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #111827; color: #111827;
} }
} }
.subtitle { .subtitle {
font-size: 14px; font-size: 14px;
color: #6b7280; color: #6b7280;
} }
.preview-body { .preview-body {
padding: 12px; padding: 12px;
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
} }
.check-list { .check-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
} }
.check-item { .check-item {
display: flex; display: flex;
gap: 12px; gap: 12px;
padding: 12px; padding: 12px;
background: #fff; background: #fff;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: 8px; border-radius: 8px;
&.success { &.success {
background: #f0fdf4; background: #f0fdf4;
border-color: #86efac; border-color: #86efac;
} }
} }
.item-icon { .item-icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
color: #ef4444; color: #ef4444;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&.warning { &.warning {
color: #f59e0b; color: #f59e0b;
} }
&.success { &.success {
color: #22c55e; color: #22c55e;
} }
.el-icon { .el-icon {
font-size: 20px; font-size: 20px;
} }
} }
.item-content { .item-content {
flex: 1; flex: 1;
} }
.item-type { .item-type {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: #f59e0b; color: #f59e0b;
margin-bottom: 4px; margin-bottom: 4px;
&.success { &.success {
color: #22c55e; color: #22c55e;
} }
} }
.item-message { .item-message {
font-size: 14px; font-size: 14px;
color: #4b5563; color: #4b5563;
line-height: 1.4; line-height: 1.4;
} }
.close-btn { .close-btn {
padding: 4px; padding: 4px;
font-size: 18px; font-size: 18px;
} }
</style> </style>

View File

@@ -13,16 +13,13 @@
<div class="p-4 flex flex-col h-[calc(100vh-150px)]"> <div class="p-4 flex flex-col h-[calc(100vh-150px)]">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<h3 class="text-base font-medium text-gray-700">AI对话</h3> <h3 class="text-base font-medium text-gray-700">AI对话</h3>
<div class="flex"> <div class="flex">
<!-- 流式调用开关 --> <!-- 流式调用开关 -->
<div class="flex items-center"> <div class="flex items-center">
<span class="text-sm text-gray-600 mr-2">流式:</span> <span class="text-sm text-gray-600 mr-2">流式:</span>
<el-switch <el-switch v-model="chat.isStream" class="mr-4" />
v-model="chat.isStream" </div>
class="mr-4"
/>
</div>
<el-button link type="primary" class="flex text-gray-500 hover:text-blue-500" @click="chat.showParams = !chat.showParams"> <el-button link type="primary" class="flex text-gray-500 hover:text-blue-500" @click="chat.showParams = !chat.showParams">
<i class="mr-1 text-lg i-tabler-settings"></i> <i class="mr-1 text-lg i-tabler-settings"></i>
<span class="text-sm">{{ chat.showParams ? '隐藏设置' : '显示设置' }}</span> <span class="text-sm">{{ chat.showParams ? '隐藏设置' : '显示设置' }}</span>
@@ -384,12 +381,15 @@ const scrollToBottom = (smooth = true) => {
} }
// 确保滚动生效的备用方案 // 确保滚动生效的备用方案
setTimeout(() => { setTimeout(
if (wrapper.scrollTop + wrapper.clientHeight < scrollHeight - 10) { () => {
chatScrollbarRef.value.setScrollTop(scrollHeight); if (wrapper.scrollTop + wrapper.clientHeight < scrollHeight - 10) {
} chatScrollbarRef.value.setScrollTop(scrollHeight);
isAtBottom.value = true; }
}, smooth ? 100 : 10); isAtBottom.value = true;
},
smooth ? 100 : 10
);
}); });
} catch (error) { } catch (error) {
// 静默处理错误 // 静默处理错误
@@ -400,19 +400,19 @@ const scrollToBottom = (smooth = true) => {
// 专门用于流式传输的滚动函数(带防抖) // 专门用于流式传输的滚动函数(带防抖)
const scrollToBottomForStreaming = () => { const scrollToBottomForStreaming = () => {
if (!chatScrollbarRef.value || streamingMessageIndex.value === -1) return; if (!chatScrollbarRef.value || streamingMessageIndex.value === -1) return;
// 清除之前的定时器 // 清除之前的定时器
if (scrollDebounceTimer) { if (scrollDebounceTimer) {
clearTimeout(scrollDebounceTimer); clearTimeout(scrollDebounceTimer);
} }
// 设置防抖,避免过度频繁的滚动 // 设置防抖,避免过度频繁的滚动
scrollDebounceTimer = setTimeout(() => { scrollDebounceTimer = setTimeout(() => {
nextTick(() => { nextTick(() => {
try { try {
const wrapper = chatScrollbarRef.value.wrapRef; const wrapper = chatScrollbarRef.value.wrapRef;
if (!wrapper) return; if (!wrapper) return;
// 检查是否需要滚动(只有在用户在底部时才自动滚动) // 检查是否需要滚动(只有在用户在底部时才自动滚动)
if (isAtBottom.value) { if (isAtBottom.value) {
const scrollHeight = wrapper.scrollHeight; const scrollHeight = wrapper.scrollHeight;
@@ -471,13 +471,13 @@ const handleClear = async () => {
isExecuting.value = false; isExecuting.value = false;
streamingMessageIndex.value = -1; streamingMessageIndex.value = -1;
currentStreamContent.value = ''; currentStreamContent.value = '';
// 清理防抖定时器 // 清理防抖定时器
if (scrollDebounceTimer) { if (scrollDebounceTimer) {
clearTimeout(scrollDebounceTimer); clearTimeout(scrollDebounceTimer);
scrollDebounceTimer = null; scrollDebounceTimer = null;
} }
ElMessage.success('聊天记录已清空'); ElMessage.success('聊天记录已清空');
}; };
@@ -536,7 +536,7 @@ const executionStatus = computed(() => {
if (props.finalResult?.result?.nodes && props.finalResult.result.nodes.length > 0) { if (props.finalResult?.result?.nodes && props.finalResult.result.nodes.length > 0) {
const nodes = props.finalResult.result.nodes; const nodes = props.finalResult.result.nodes;
const lastNode = nodes[nodes.length - 1]; const lastNode = nodes[nodes.length - 1];
if (lastNode && lastNode.status) { if (lastNode && lastNode.status) {
const statusMap = { const statusMap = {
running: { text: '运行中', class: 'status-running' }, running: { text: '运行中', class: 'status-running' },
@@ -547,7 +547,7 @@ const executionStatus = computed(() => {
return statusMap[lastNode.status] || { text: '等待中', class: 'status-pending' }; return statusMap[lastNode.status] || { text: '等待中', class: 'status-pending' };
} }
} }
// 如果没有 finalResult 的 nodes则使用 executionNodes // 如果没有 finalResult 的 nodes则使用 executionNodes
const lastNode = props.executionNodes[props.executionNodes.length - 1]; const lastNode = props.executionNodes[props.executionNodes.length - 1];
@@ -685,7 +685,7 @@ const sendMessage = () => {
content: '', content: '',
isStreaming: true, isStreaming: true,
}); });
streamingMessageIndex.value = aiMessageIndex; streamingMessageIndex.value = aiMessageIndex;
currentStreamContent.value = ''; currentStreamContent.value = '';
// 由于已经有流式消息占位符,不需要额外的 loading 状态 // 由于已经有流式消息占位符,不需要额外的 loading 状态
@@ -695,7 +695,7 @@ const sendMessage = () => {
isLoading.value = true; isLoading.value = true;
streamingMessageIndex.value = -1; streamingMessageIndex.value = -1;
} }
userInput.value = ''; userInput.value = '';
// Force scroll to bottom after sending message // Force scroll to bottom after sending message
@@ -717,7 +717,7 @@ watch(
if (currentMessage && currentMessage.role === 'ai') { if (currentMessage && currentMessage.role === 'ai') {
// 更新流式消息内容 // 更新流式消息内容
currentMessage.content = newResult.chatMessage; currentMessage.content = newResult.chatMessage;
// 检查是否完成流式传输 // 检查是否完成流式传输
if (!newResult.isStreaming) { if (!newResult.isStreaming) {
currentMessage.isStreaming = false; currentMessage.isStreaming = false;
@@ -725,7 +725,7 @@ watch(
isLoading.value = false; isLoading.value = false;
isExecuting.value = false; isExecuting.value = false;
} }
// 滚动到底部(流式传输时使用专门的滚动函数) // 滚动到底部(流式传输时使用专门的滚动函数)
if (newResult.isStreaming) { if (newResult.isStreaming) {
scrollToBottomForStreaming(); scrollToBottomForStreaming();
@@ -742,7 +742,7 @@ watch(
// 如果没有流式消息,但有结果,显示传统格式 // 如果没有流式消息,但有结果,显示传统格式
if (newResult.result) { if (newResult.result) {
let responseText = ''; let responseText = '';
// 如果有聊天消息字段,优先显示 // 如果有聊天消息字段,优先显示
if (newResult.chatMessage) { if (newResult.chatMessage) {
responseText = newResult.chatMessage; responseText = newResult.chatMessage;
@@ -1049,8 +1049,6 @@ onUpdated(() => {
} }
} }
@keyframes bounce { @keyframes bounce {
0%, 0%,
80%, 80%,

View File

@@ -1,196 +1,166 @@
<template> <template>
<!-- 开场白和开场问题编辑器 -->
<el-dialog v-model="dialogVisible" title="提示词" width="600px" :show-footer="false">
<div class="greeting-editor">
<!-- 开场白编辑区 -->
<div class="greeting-section">
<div class="section-title">
<div class="title-with-help">
<span>聊天开场白</span>
<el-tooltip content="开场白会在用户进入对话时首先展示用于介绍AI助手的功能和特点。" placement="top">
<el-icon class="help-icon">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<el-input v-model="form.greeting" type="textarea" :rows="5" class="greeting-input" placeholder="在这里编写AI助手的开场白" />
</div>
<!-- 开场白和开场问题编辑 --> <!-- 开场问题编辑 -->
<el-dialog <div class="questions-section">
v-model="dialogVisible" <div class="section-title">
title="提示词" <div class="title-with-help">
width="600px" <span>开场问题</span>
:show-footer="false" <div class="question-count">{{ form.questions.length }}/10</div>
> <el-tooltip content="设置常见问题示例帮助用户快速开始对话。最多可设置10个问题。" placement="top">
<div class="greeting-editor"> <el-icon class="help-icon">
<!-- 开场白编辑区 --> <QuestionFilled />
<div class="greeting-section"> </el-icon>
<div class="section-title"> </el-tooltip>
<div class="title-with-help"> </div>
<span>聊天开场白</span> <el-button type="primary" class="add-question" @click="addQuestion" :disabled="form.questions.length >= 10" size="small">
<el-tooltip content="开场白会在用户进入对话时首先展示用于介绍AI助手的功能和特点。" <el-icon class="icon-plus">
placement="top"> <Plus />
<el-icon class="help-icon"> </el-icon>
<QuestionFilled /> 添加
</el-icon> </el-button>
</el-tooltip> </div>
</div> <div class="questions-list">
</div> <div v-for="(question, index) in form.questions" :key="index" class="question-item">
<el-input <el-input v-model="question.text" class="question-input" :placeholder="'问题 ' + (index + 1)" />
v-model="form.greeting" <el-button @click="removeQuestion(index)" type="danger" :icon="Delete" circle size="small" />
type="textarea" </div>
:rows="5" </div>
class="greeting-input" </div>
placeholder="在这里编写AI助手的开场白" </div>
/> </el-dialog>
</div>
<!-- 开场问题编辑区 -->
<div class="questions-section">
<div class="section-title">
<div class="title-with-help">
<span>开场问题</span>
<div class="question-count">{{ form.questions.length }}/10</div>
<el-tooltip content="设置常见问题示例帮助用户快速开始对话。最多可设置10个问题。"
placement="top">
<el-icon class="help-icon">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
<el-button
type="primary"
class="add-question"
@click="addQuestion"
:disabled="form.questions.length >= 10"
size="small"
>
<el-icon class="icon-plus">
<Plus />
</el-icon>
添加
</el-button>
</div>
<div class="questions-list">
<div v-for="(question, index) in form.questions"
:key="index"
class="question-item">
<el-input
v-model="question.text"
class="question-input"
:placeholder="'问题 ' + (index + 1)"
/>
<el-button
@click="removeQuestion(index)"
type="danger"
:icon="Delete"
circle
size="small"
/>
</div>
</div>
</div>
</div>
</el-dialog>
</template> </template>
<script setup> <script setup>
import { QuestionFilled, Plus, Delete } from '@element-plus/icons-vue' import { QuestionFilled, Plus, Delete } from '@element-plus/icons-vue';
// 注入parent // 注入parent
const parent = inject('parent') const parent = inject('parent');
// 定义组件属性 // 定义组件属性
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}) });
// 定义事件 // 定义事件
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue']);
// 对话框显示状态 // 对话框显示状态
const dialogVisible = ref(props.modelValue) const dialogVisible = ref(props.modelValue);
const form = ref({ const form = ref({
questions: [] questions: [],
}) });
// 监听modelValue的变化 // 监听modelValue的变化
watch(() => props.modelValue, (newVal) => { watch(
form.value = parent.dsl; () => props.modelValue,
form.value.questions=form.value.questions || [] (newVal) => {
dialogVisible.value = newVal form.value = parent.dsl;
}) form.value.questions = form.value.questions || [];
dialogVisible.value = newVal;
}
);
// 监听dialogVisible的变化 // 监听dialogVisible的变化
watch(() => dialogVisible.value, (newVal) => { watch(
emit('update:modelValue', newVal) () => dialogVisible.value,
}) (newVal) => {
emit('update:modelValue', newVal);
}
);
// 添加问题 // 添加问题
const addQuestion = () => { const addQuestion = () => {
if (form.value.questions.length < 10) { if (form.value.questions.length < 10) {
form.value.questions.push({ text: '' }) form.value.questions.push({ text: '' });
} }
} };
// 删除问题 // 删除问题
const removeQuestion = (index) => { const removeQuestion = (index) => {
form.value.questions.splice(index, 1) form.value.questions.splice(index, 1);
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.greeting-editor { .greeting-editor {
.greeting-section, .greeting-section,
.questions-section { .questions-section {
margin-bottom: 20px; margin-bottom: 20px;
} }
.section-title { .section-title {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
.title-with-help { .title-with-help {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.help-icon { .help-icon {
font-size: 16px; font-size: 16px;
color: #909399; color: #909399;
cursor: help; cursor: help;
} }
.question-count { .question-count {
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
margin: 0 8px; margin: 0 8px;
} }
} }
} }
.greeting-input { .greeting-input {
:deep(.el-textarea__inner) { :deep(.el-textarea__inner) {
min-height: 60px; min-height: 60px;
max-height: 120px; max-height: 120px;
} }
} }
.questions-list { .questions-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
.question-item { .question-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.question-input { .question-input {
flex: 1; flex: 1;
} }
} }
} }
} }
.dialog-footer { .dialog-footer {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 10px; gap: 10px;
} }
</style> </style>

View File

@@ -1,174 +1,164 @@
<template> <template>
<el-dialog <el-dialog
title="数据结构" title="数据结构"
v-model="dialogVisible" v-model="dialogVisible"
width="800px" width="800px"
class="json-preview-dialog" class="json-preview-dialog"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
> >
<div class="preview-content"> <div class="preview-content">
<!-- 顶部标签页 --> <!-- 顶部标签页 -->
<el-tabs v-model="activeTab" class="preview-tabs"> <el-tabs v-model="activeTab" class="preview-tabs">
<el-tab-pane <el-tab-pane v-for="tab in tabs" :key="tab.key" :label="tab.label" :name="tab.key">
v-for="tab in tabs" <template #label>
:key="tab.key" <span>{{ tab.label }}</span>
:label="tab.label" <el-tag v-if="tab.key === 'nodes'" size="small" type="info" class="ml-2">
:name="tab.key" {{ nodeCount }}
> </el-tag>
<template #label> </template>
<span>{{ tab.label }}</span> </el-tab-pane>
<el-tag v-if="tab.key === 'nodes'" size="small" type="info" class="ml-2"> </el-tabs>
{{ nodeCount }}
</el-tag>
</template>
</el-tab-pane>
</el-tabs>
<!-- 代码编辑器 --> <!-- 代码编辑器 -->
<div class="preview-body"> <div class="preview-body">
<code-editor <code-editor v-model="currentTabData" :json="true" :readonly="false" theme="nord" height="400px" />
v-model="currentTabData" </div>
:json="true" </div>
:readonly="false" <template #footer>
theme="nord" <div class="dialog-footer">
height="400px" <el-button type="primary" @click="copyData">
/> <el-icon><Document /></el-icon>
</div> 复制内容
</div> </el-button>
<template #footer> <el-button @click="dialogVisible = false">关闭</el-button>
<div class="dialog-footer"> </div>
<el-button type="primary" @click="copyData"> </template>
<el-icon><Document /></el-icon> </el-dialog>
复制内容
</el-button>
<el-button @click="dialogVisible = false">关闭</el-button>
</div>
</template>
</el-dialog>
</template> </template>
<script> <script>
import { Document } from '@element-plus/icons-vue' import { Document } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus';
import CodeEditor from "/@/views/knowledge/aiFlow/components/CodeEditor.vue"; import CodeEditor from '/@/views/knowledge/aiFlow/components/CodeEditor.vue';
export default { export default {
name: 'JsonPreviewPanel', name: 'JsonPreviewPanel',
components: { components: {
CodeEditor, CodeEditor,
Document Document,
}, },
inject: ['parent'], inject: ['parent'],
props: { props: {
modelValue: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
data() { data() {
return { return {
activeTab: 'all', activeTab: 'all',
tabs: [ tabs: [
{ key: 'all', label: '全部数据' }, { key: 'all', label: '全部数据' },
{ key: 'nodes', label: '节点数据' }, { key: 'nodes', label: '节点数据' },
{ key: 'connections', label: '连线数据' }, { key: 'connections', label: '连线数据' },
{ key: 'execution', label: '执行顺序' } { key: 'execution', label: '执行顺序' },
] ],
} };
}, },
computed: { computed: {
dialogVisible: { dialogVisible: {
get() { get() {
return this.modelValue return this.modelValue;
}, },
set(value) { set(value) {
this.$emit('update:modelValue', value) this.$emit('update:modelValue', value);
} },
}, },
data() { data() {
return { return {
nodes: this.parent.nodes, nodes: this.parent.nodes,
connections: this.parent.connections, connections: this.parent.connections,
executionOrder: this.parent.workflowExecutionOrder, executionOrder: this.parent.workflowExecutionOrder,
} };
}, },
nodeCount() { nodeCount() {
return this.data.nodes ? this.data.nodes.length : 0 return this.data.nodes ? this.data.nodes.length : 0;
}, },
currentTabData: { currentTabData: {
get() { get() {
let data = '' let data = '';
switch (this.activeTab) { switch (this.activeTab) {
case 'nodes': case 'nodes':
data = this.data.nodes data = this.data.nodes;
break break;
case 'connections': case 'connections':
data = this.data.connections data = this.data.connections;
break break;
case 'execution': case 'execution':
data = this.data.executionOrder data = this.data.executionOrder;
break break;
default: default:
data = this.data data = this.data;
} }
return JSON.stringify(data, null, 2) return JSON.stringify(data, null, 2);
}, },
set() { set() {
// 只读模式不需要实现set // 只读模式不需要实现set
} },
} },
}, },
methods: { methods: {
copyData() { copyData() {
navigator.clipboard.writeText(this.currentTabData) navigator.clipboard
.then(() => { .writeText(this.currentTabData)
ElMessage({ .then(() => {
message: '复制成功', ElMessage({
type: 'success', message: '复制成功',
duration: 2000 type: 'success',
}) duration: 2000,
}) });
.catch(err => { })
ElMessage({ .catch((err) => {
message: '复制失败', ElMessage({
type: 'error', message: '复制失败',
duration: 2000 type: 'error',
}) duration: 2000,
console.error('复制失败:', err) });
}) console.error('复制失败:', err);
} });
} },
} },
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.json-preview-dialog { .json-preview-dialog {
:deep(.el-dialog__body) { :deep(.el-dialog__body) {
padding: 0; padding: 0;
} }
.preview-content { .preview-content {
.preview-tabs { .preview-tabs {
padding: 0 15px; padding: 0 15px;
} }
.preview-body { .preview-body {
padding: 10px 15px; padding: 10px 15px;
} }
} }
.dialog-footer { .dialog-footer {
padding: 15px; padding: 15px;
text-align: right; text-align: right;
} }
:deep(.el-tabs__header) { :deep(.el-tabs__header) {
margin-bottom: 15px; margin-bottom: 15px;
} }
:deep(.el-tag) { :deep(.el-tag) {
margin-left: 5px; margin-left: 5px;
} }
} }
</style> </style>

View File

@@ -1,329 +1,327 @@
<!-- 缩略图组件 --> <!-- 缩略图组件 -->
<template> <template>
<div class="mini-map" :style="{ width: width + 'px', height: height + 'px' }"> <div class="mini-map" :style="{ width: width + 'px', height: height + 'px' }">
<!-- 缩略图容器 --> <!-- 缩略图容器 -->
<div class="mini-map-container" ref="container"> <div class="mini-map-container" ref="container">
<!-- 缩略图内容 --> <!-- 缩略图内容 -->
<div class="mini-map-content" <div class="mini-map-content" :style="{ transform: `translate(${contentPosition.x}px, ${contentPosition.y}px) scale(${scale})` }">
:style="{ transform: `translate(${contentPosition.x}px, ${contentPosition.y}px) scale(${scale})` }"> <!-- 节点缩略图 -->
<!-- 节点缩略图 --> <div
<div v-for="node in nodes" v-for="node in nodes"
:key="node.id" :key="node.id"
class="mini-node" class="mini-node"
:style="{ :style="{
left: `${node.x}px`, left: `${node.x}px`,
top: `${node.y}px`, top: `${node.y}px`,
backgroundColor: getNodeColor(node.type) backgroundColor: getNodeColor(node.type),
}"> }"
</div> ></div>
<!-- 连线缩略图 --> <!-- 连线缩略图 -->
<svg class="mini-connections" :style="{ width: `${bounds.width}px`, height: `${bounds.height}px` }"> <svg class="mini-connections" :style="{ width: `${bounds.width}px`, height: `${bounds.height}px` }">
<path v-for="(conn, index) in connections" <path v-for="(conn, index) in connections" :key="index" :d="getConnectionPath(conn)" class="mini-connection-path" />
:key="index" </svg>
:d="getConnectionPath(conn)" </div>
class="mini-connection-path"/> <!-- 视口指示器 -->
</svg> <div
</div> class="viewport-indicator"
<!-- 视口指示器 --> :style="{
<div class="viewport-indicator" transform: `translate(${viewportPosition.x}px, ${viewportPosition.y}px)`,
:style="{ width: `${viewportSize.width}px`,
transform: `translate(${viewportPosition.x}px, ${viewportPosition.y}px)`, height: `${viewportSize.height}px`,
width: `${viewportSize.width}px`, }"
height: `${viewportSize.height}px` @mousedown.prevent="startDrag"
}" ></div>
@mousedown.prevent="startDrag"> </div>
</div> </div>
</div>
</div>
</template> </template>
<script> <script>
export default { export default {
name: 'MiniMap', name: 'MiniMap',
props: { props: {
// 缩略图宽度 // 缩略图宽度
width: { width: {
type: Number, type: Number,
default: 200 default: 200,
}, },
// 缩略图高度 // 缩略图高度
height: { height: {
type: Number, type: Number,
default: 150 default: 150,
}, },
// 节点数据 // 节点数据
nodes: { nodes: {
type: Array, type: Array,
default: () => [] default: () => [],
}, },
// 连接数据 // 连接数据
connections: { connections: {
type: Array, type: Array,
default: () => [] default: () => [],
}, },
// 画布缩放比例 // 画布缩放比例
zoom: { zoom: {
type: Number, type: Number,
default: 1 default: 1,
}, },
// 画布位置 // 画布位置
position: { position: {
type: Object, type: Object,
default: () => ({ x: 0, y: 0 }) default: () => ({ x: 0, y: 0 }),
}, },
// 添加容器尺寸属性 // 添加容器尺寸属性
containerSize: { containerSize: {
type: Object, type: Object,
default: () => ({ default: () => ({
width: 0, width: 0,
height: 0 height: 0,
}) }),
} },
}, },
data() { data() {
return { return {
scale: 0.1, scale: 0.1,
isDragging: false, isDragging: false,
dragStart: { x: 0, y: 0 }, dragStart: { x: 0, y: 0 },
bounds: { bounds: {
minX: 0, minX: 0,
minY: 0, minY: 0,
width: 3000, width: 3000,
height: 3000 height: 3000,
} },
} };
}, },
computed: { computed: {
// 计算内容位置,使其居中显示 // 计算内容位置,使其居中显示
contentPosition() { contentPosition() {
const offsetX = (this.width - this.bounds.width * this.scale) / 2 const offsetX = (this.width - this.bounds.width * this.scale) / 2;
const offsetY = (this.height - this.bounds.height * this.scale) / 2 const offsetY = (this.height - this.bounds.height * this.scale) / 2;
return { return {
x: offsetX, x: offsetX,
y: offsetY y: offsetY,
} };
}, },
// 修改视口位置计算 // 修改视口位置计算
viewportPosition() { viewportPosition() {
// 确保位置不会超出边界 // 确保位置不会超出边界
const maxX = this.width - this.viewportSize.width const maxX = this.width - this.viewportSize.width;
const maxY = this.height - this.viewportSize.height const maxY = this.height - this.viewportSize.height;
let x = (-this.position.x * this.scale) + this.contentPosition.x
let y = (-this.position.y * this.scale) + this.contentPosition.y
// 限制在有效范围内
x = Math.max(0, Math.min(x, maxX))
y = Math.max(0, Math.min(y, maxY))
return { x, y }
},
// 修改视口尺寸计算
viewportSize() {
// 计算缩略图内容的实际显示范围
const contentWidth = this.bounds.width * this.scale
const contentHeight = this.bounds.height * this.scale
// 计算视口尺寸比例
const viewportRatioX = this.width / (this.bounds.width / this.zoom)
const viewportRatioY = this.height / (this.bounds.height / this.zoom)
// 确保视口尺寸不会小于最小值或大于缩略图尺寸
return {
width: Math.min(this.width, Math.max(50, contentWidth * viewportRatioX)),
height: Math.min(this.height, Math.max(30, contentHeight * viewportRatioY))
}
}
},
watch: {
// 监听节点变化,重新计算边界和缩放
nodes: {
handler() {
this.$nextTick(this.updateBoundsAndScale)
},
deep: true
}
},
methods: {
// 获取节点颜色
getNodeColor(type) {
const colors = {
start: '#10B981',
end: '#EF4444',
http: '#3B82F6',
switch: '#F59E0B',
code: '#8B5CF6',
db: '#6366F1',
llm: '#EC4899',
notice: '#14B8A6',
question: '#F97316',
default: '#6B7280'
}
return colors[type] || colors.default
},
// 获取连接路径
getConnectionPath(conn) {
const source = this.nodes.find(n => n.id === conn.sourceId)
const target = this.nodes.find(n => n.id === conn.targetId)
if (!source || !target) return ''
const x1 = source.x let x = -this.position.x * this.scale + this.contentPosition.x;
const y1 = source.y let y = -this.position.y * this.scale + this.contentPosition.y;
const x2 = target.x
const y2 = target.y
// 计算控制点 // 限制在有效范围内
const dx = Math.abs(x2 - x1) * 0.5 x = Math.max(0, Math.min(x, maxX));
const cp1x = x1 + dx y = Math.max(0, Math.min(y, maxY));
const cp1y = y1
const cp2x = x2 - dx
const cp2y = y2
return `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}` return { x, y };
}, },
// 开始拖动视口 // 修改视口尺寸计算
startDrag(event) { viewportSize() {
this.isDragging = true // 计算缩略图内容的实际显示范围
const rect = this.$refs.container.getBoundingClientRect() const contentWidth = this.bounds.width * this.scale;
this.dragStart = { const contentHeight = this.bounds.height * this.scale;
x: event.clientX - rect.left - this.viewportPosition.x,
y: event.clientY - rect.top - this.viewportPosition.y
}
window.addEventListener('mousemove', this.onDrag)
window.addEventListener('mouseup', this.stopDrag)
},
// 拖动中
onDrag(event) {
if (!this.isDragging) return
const rect = this.$refs.container.getBoundingClientRect()
let x = event.clientX - rect.left - this.dragStart.x
let y = event.clientY - rect.top - this.dragStart.y
// 添加边界限制
const maxX = this.width - this.viewportSize.width
const maxY = this.height - this.viewportSize.height
x = Math.max(0, Math.min(x, maxX))
y = Math.max(0, Math.min(y, maxY))
// 计算相对于内容的位置
const relativeX = (x - this.contentPosition.x) / this.scale
const relativeY = (y - this.contentPosition.y) / this.scale
this.$emit('update:position', {
x: -relativeX,
y: -relativeY
})
},
// 停止拖动
stopDrag() {
this.isDragging = false
window.removeEventListener('mousemove', this.onDrag)
window.removeEventListener('mouseup', this.stopDrag)
},
// 更新边界和缩放
updateBoundsAndScale() {
if (!this.nodes.length) return
// 计算节点边界 // 计算视口尺寸比例
const nodePositions = this.nodes.map(node => ({ const viewportRatioX = this.width / (this.bounds.width / this.zoom);
left: node.x - 100, // 考虑节点宽度 const viewportRatioY = this.height / (this.bounds.height / this.zoom);
right: node.x + 100,
top: node.y - 50, // 考虑节点高度
bottom: node.y + 50
}))
// 计算整体边界 // 确保视口尺寸不会小于最小值或大于缩略图尺寸
const minX = Math.min(...nodePositions.map(p => p.left)) return {
const maxX = Math.max(...nodePositions.map(p => p.right)) width: Math.min(this.width, Math.max(50, contentWidth * viewportRatioX)),
const minY = Math.min(...nodePositions.map(p => p.top)) height: Math.min(this.height, Math.max(30, contentHeight * viewportRatioY)),
const maxY = Math.max(...nodePositions.map(p => p.bottom)) };
},
},
watch: {
// 监听节点变化,重新计算边界和缩放
nodes: {
handler() {
this.$nextTick(this.updateBoundsAndScale);
},
deep: true,
},
},
methods: {
// 获取节点颜色
getNodeColor(type) {
const colors = {
start: '#10B981',
end: '#EF4444',
http: '#3B82F6',
switch: '#F59E0B',
code: '#8B5CF6',
db: '#6366F1',
llm: '#EC4899',
notice: '#14B8A6',
question: '#F97316',
default: '#6B7280',
};
return colors[type] || colors.default;
},
// 获取连接路径
getConnectionPath(conn) {
const source = this.nodes.find((n) => n.id === conn.sourceId);
const target = this.nodes.find((n) => n.id === conn.targetId);
if (!source || !target) return '';
// 添加边距 const x1 = source.x;
const PADDING = 100 const y1 = source.y;
this.bounds = { const x2 = target.x;
minX: minX - PADDING, const y2 = target.y;
minY: minY - PADDING,
width: maxX - minX + PADDING * 2,
height: maxY - minY + PADDING * 2
}
// 计算合适的缩放比例 // 计算控制点
const scaleX = this.width / this.bounds.width const dx = Math.abs(x2 - x1) * 0.5;
const scaleY = this.height / this.bounds.height const cp1x = x1 + dx;
this.scale = Math.min(scaleX, scaleY, 0.2) // 限制最大缩放比例为0.2 const cp1y = y1;
} const cp2x = x2 - dx;
}, const cp2y = y2;
mounted() {
this.updateBoundsAndScale() return `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`;
} },
} // 开始拖动视口
startDrag(event) {
this.isDragging = true;
const rect = this.$refs.container.getBoundingClientRect();
this.dragStart = {
x: event.clientX - rect.left - this.viewportPosition.x,
y: event.clientY - rect.top - this.viewportPosition.y,
};
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.stopDrag);
},
// 拖动中
onDrag(event) {
if (!this.isDragging) return;
const rect = this.$refs.container.getBoundingClientRect();
let x = event.clientX - rect.left - this.dragStart.x;
let y = event.clientY - rect.top - this.dragStart.y;
// 添加边界限制
const maxX = this.width - this.viewportSize.width;
const maxY = this.height - this.viewportSize.height;
x = Math.max(0, Math.min(x, maxX));
y = Math.max(0, Math.min(y, maxY));
// 计算相对于内容的位置
const relativeX = (x - this.contentPosition.x) / this.scale;
const relativeY = (y - this.contentPosition.y) / this.scale;
this.$emit('update:position', {
x: -relativeX,
y: -relativeY,
});
},
// 停止拖动
stopDrag() {
this.isDragging = false;
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.stopDrag);
},
// 更新边界和缩放
updateBoundsAndScale() {
if (!this.nodes.length) return;
// 计算节点边界
const nodePositions = this.nodes.map((node) => ({
left: node.x - 100, // 考虑节点宽度
right: node.x + 100,
top: node.y - 50, // 考虑节点高度
bottom: node.y + 50,
}));
// 计算整体边界
const minX = Math.min(...nodePositions.map((p) => p.left));
const maxX = Math.max(...nodePositions.map((p) => p.right));
const minY = Math.min(...nodePositions.map((p) => p.top));
const maxY = Math.max(...nodePositions.map((p) => p.bottom));
// 添加边距
const PADDING = 100;
this.bounds = {
minX: minX - PADDING,
minY: minY - PADDING,
width: maxX - minX + PADDING * 2,
height: maxY - minY + PADDING * 2,
};
// 计算合适的缩放比例
const scaleX = this.width / this.bounds.width;
const scaleY = this.height / this.bounds.height;
this.scale = Math.min(scaleX, scaleY, 0.2); // 限制最大缩放比例为0.2
},
},
mounted() {
this.updateBoundsAndScale();
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mini-map { .mini-map {
position: absolute; position: absolute;
left: 20px; left: 20px;
bottom: 60px; bottom: 60px;
background: #fff; background: #fff;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden; overflow: hidden;
z-index: 1; z-index: 1;
.mini-map-container { .mini-map-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
.mini-map-content { .mini-map-content {
position: absolute; position: absolute;
transform-origin: 0 0; transform-origin: 0 0;
} }
.mini-node { .mini-node {
position: absolute; position: absolute;
width: 20px; width: 20px;
height: 10px; height: 10px;
border-radius: 2px; border-radius: 2px;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.mini-connections { .mini-connections {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
pointer-events: none; pointer-events: none;
.mini-connection-path { .mini-connection-path {
fill: none; fill: none;
stroke: #94a3b8; stroke: #94a3b8;
stroke-width: 1px; stroke-width: 1px;
} }
} }
.viewport-indicator { .viewport-indicator {
position: absolute; position: absolute;
border: 1px solid #3b82f6; border: 1px solid #3b82f6;
background: rgba(59, 130, 246, 0.05); // 降低默认透明度,提高对比度 background: rgba(59, 130, 246, 0.05); // 降低默认透明度,提高对比度
pointer-events: all; pointer-events: all;
cursor: move; cursor: move;
border-radius: 4px; // 添加圆角 border-radius: 4px; // 添加圆角
transition: all 0.2s ease; // 添加过渡动画 transition: all 0.2s ease; // 添加过渡动画
&:hover { &:hover {
background: rgba(59, 130, 246, 0.15); // 提高hover时的透明度 background: rgba(59, 130, 246, 0.15); // 提高hover时的透明度
border-color: #2563eb; // hover时加深边框颜色 border-color: #2563eb; // hover时加深边框颜色
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); // hover时加深阴影 box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); // hover时加深阴影
} }
&:active { &:active {
background: rgba(59, 130, 246, 0.2); // 点击时加深背景色 background: rgba(59, 130, 246, 0.2); // 点击时加深背景色
border-color: #1d4ed8; // 点击时进一步加深边框 border-color: #1d4ed8; // 点击时进一步加深边框
} }
} }
} }
</style> </style>

View File

@@ -60,15 +60,15 @@ export default {
if (this.addPosition !== 'right' || !this.node || !this.parent) { if (this.addPosition !== 'right' || !this.node || !this.parent) {
return true; return true;
} }
// 获取当前节点已有的连接数量 // 获取当前节点已有的连接数量
const existingConnections = this.parent.connections?.filter((conn) => conn.sourceId === this.node.id) || []; const existingConnections = this.parent.connections?.filter((conn) => conn.sourceId === this.node.id) || [];
// 如果不是分支节点且已经有连接,则不能向右添加 // 如果不是分支节点且已经有连接,则不能向右添加
if (!['switch', 'question'].includes(this.node.type) && existingConnections.length > 0) { if (!['switch', 'question'].includes(this.node.type) && existingConnections.length > 0) {
return false; return false;
} }
return true; return true;
}, },
nodeTypes() { nodeTypes() {

View File

@@ -1,313 +1,304 @@
<template> <template>
<div> <div>
<div v-if="visible" <div v-if="visible" class="more-menu" :style="menuStyle">
class="more-menu" <!-- 拷贝 -->
:style="menuStyle"> <div class="menu-item" @click="copyNode">
<!-- 拷贝 --> <span>复制</span>
<div class="menu-item" </div>
@click="copyNode">
<span>复制</span>
</div>
<!-- 新增子节点 --> <!-- 新增子节点 -->
<div class="menu-item" <div class="menu-item" @click="handleShowAddChild" v-if="canAddChild">
@click="handleShowAddChild" <span>新增子节点</span>
v-if="canAddChild"> <i class="el-icon-arrow-right"></i>
<span>新增子节点</span> </div>
<i class="el-icon-arrow-right"></i>
</div>
<!-- 更换类型 --> <!-- 更换类型 -->
<div class="menu-item" <div class="menu-item" @click="handleShowChangeType">
@click="handleShowChangeType"> <span>更换类型</span>
<span>更换类型</span> <i class="el-icon-arrow-right"></i>
<i class="el-icon-arrow-right"></i> </div>
</div>
<!-- 删除 --> <!-- 删除 -->
<div class="menu-item" <div class="menu-item" @click="deleteNode" v-if="node.type !== 'start'">
@click="deleteNode" v-if="node.type !== 'start'"> <span>删除</span>
<span>删除</span> </div>
</div> </div>
</div>
<!-- 使用 NodeContextMenu 组件 - 更换类型 --> <!-- 使用 NodeContextMenu 组件 - 更换类型 -->
<NodeContextMenu v-model:visible="showChangeType" <NodeContextMenu
:position="changeTypeMenuPosition" v-model:visible="showChangeType"
:node="node" :position="changeTypeMenuPosition"
add-position="replace" :node="node"
@add="handleChangeType" /> add-position="replace"
@add="handleChangeType"
/>
<!-- 使用 NodeContextMenu 组件 - 新增子节点 --> <!-- 使用 NodeContextMenu 组件 - 新增子节点 -->
<NodeContextMenu v-model:visible="showAddChild" <NodeContextMenu v-model:visible="showAddChild" :position="addChildMenuPosition" :node="node" add-position="right" @add="handleAddChild" />
:position="addChildMenuPosition" </div>
:node="node"
add-position="right"
@add="handleAddChild" />
</div>
</template> </template>
<script> <script>
import NodeContextMenu from './NodeContextMenu.vue' import NodeContextMenu from './NodeContextMenu.vue';
import { getNodeConfig } from './nodes/nodeTypes.ts' import { getNodeConfig } from './nodes/nodeTypes.ts';
export default { export default {
name: 'NodeMoreMenu', name: 'NodeMoreMenu',
inject: ['parent'], inject: ['parent'],
components: { components: {
NodeContextMenu NodeContextMenu,
}, },
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
position: { position: {
type: Object, type: Object,
default: () => ({ x: 0, y: 0 }) default: () => ({ x: 0, y: 0 }),
}, },
node: { node: {
type: Object, type: Object,
default: null default: null,
} },
}, },
emits: ['update:visible'], emits: ['update:visible'],
data () { data() {
return { return {
showChangeType: false, showChangeType: false,
changeTypeMenuPosition: { x: 0, y: 0 }, changeTypeMenuPosition: { x: 0, y: 0 },
showAddChild: false, showAddChild: false,
addChildMenuPosition: { x: 0, y: 0 } addChildMenuPosition: { x: 0, y: 0 },
} };
}, },
computed: { computed: {
menuStyle () { menuStyle() {
return { return {
left: `${this.position.x}px`, left: `${this.position.x}px`,
top: `${this.position.y}px` top: `${this.position.y}px`,
} };
}, },
// 判断是否可以添加子节点
canAddChild () {
if (!this.node || !this.parent) return false
// 结束节点不能添加子节点
if (this.node.type === 'end') {
return false
}
// 获取当前节点已有的连接数量
const existingConnections = this.parent.connections?.filter((conn) => conn.sourceId === this.node.id) || []
// 如果是分支节点switch 或 question可以添加子节点
if (['switch', 'question'].includes(this.node.type)) {
return true
}
// 如果不是分支节点且已经有连接,则不能添加子节点
if (existingConnections.length > 0) {
return false
}
return true
}
},
methods: {
// 复制节点
copyNode () {
if (!this.node) return
// 创建节点的深拷贝 // 判断是否可以添加子节点
const nodeCopy = JSON.parse(JSON.stringify(this.node)) canAddChild() {
if (!this.node || !this.parent) return false;
// 生成新的唯一ID // 结束节点不能添加子节点
nodeCopy.id = `node_${Date.now()}` if (this.node.type === 'end') {
return false;
}
// 设置新节点的位置(在原节点右下方20px处) // 获取当前节点已有的连接数量
nodeCopy.x = this.node.x + 20 const existingConnections = this.parent.connections?.filter((conn) => conn.sourceId === this.node.id) || [];
nodeCopy.y = this.node.y + 20
// 更新节点列表 // 如果是分支节点switch 或 question可以添加子节点
this.parent.nodes = [...this.parent.nodes, nodeCopy] if (['switch', 'question'].includes(this.node.type)) {
return true;
}
// 关闭菜单 // 如果不是分支节点且已经有连接,则不能添加子节点
this.$emit('update:visible', false) if (existingConnections.length > 0) {
}, return false;
}
// 删除节点 return true;
deleteNode () { },
if (!this.node) return },
methods: {
// 复制节点
copyNode() {
if (!this.node) return;
// 删除节点的所有端点 // 创建节点的深拷贝
this.parent.jsPlumbInstance.removeAllEndpoints(this.node.id) const nodeCopy = JSON.parse(JSON.stringify(this.node));
// 从节点列表中删除节点 // 生成新的唯一ID
this.parent.nodes= this.parent.nodes.filter(n => n.id !== this.node.id) nodeCopy.id = `node_${Date.now()}`;
// 关闭菜单 // 设置新节点的位置(在原节点右下方20px处)
this.$emit('update:visible', false) nodeCopy.x = this.node.x + 20;
}, nodeCopy.y = this.node.y + 20;
// 更节点类型 // 更节点列表
changeNodeType (newType) { this.parent.nodes = [...this.parent.nodes, nodeCopy];
if (!this.node || !newType) return
// 获取新节点类型的配置 // 关闭菜单
const nodeConfig = getNodeConfig(newType) this.$emit('update:visible', false);
},
// 找到当前节点的索引 // 删除节点
const nodeIndex = this.parent.nodes.findIndex(n => n.id === this.node.id) deleteNode() {
if (nodeIndex === -1) return if (!this.node) return;
// 保存原节点的位置和ID // 删除节点的所有端点
const { x, y, id } = this.node this.parent.jsPlumbInstance.removeAllEndpoints(this.node.id);
// 创建新节点保持原有的位置和ID // 从节点列表中删除节点
const newNode = { this.parent.nodes = this.parent.nodes.filter((n) => n.id !== this.node.id);
...nodeConfig,
id,
x,
y,
}
// 更新节点列表 // 关闭菜单
const newNodes = [...this.parent.nodes] this.$emit('update:visible', false);
newNodes[nodeIndex] = newNode },
this.parent.nodes = newNodes
// 关闭菜单 // 更换节点类型
this.$emit('update:visible', false) changeNodeType(newType) {
this.showChangeType = false if (!this.node || !newType) return;
},
handleShowChangeType (event) { // 获取新节点类型的配置
// 计算子菜单位置,在父菜单右侧显示 const nodeConfig = getNodeConfig(newType);
const menuEl = this.$el.querySelector('.more-menu')
if (menuEl) {
const rect = menuEl.getBoundingClientRect()
// 检查右侧空间是否足够
const rightSpace = window.innerWidth - rect.right
const leftSpace = rect.left
// 如果右侧空间不足,且左侧空间足够,则显示在左侧 // 找到当前节点的索引
if (rightSpace < 200 && leftSpace > 200) { const nodeIndex = this.parent.nodes.findIndex((n) => n.id === this.node.id);
this.changeTypeMenuPosition = { if (nodeIndex === -1) return;
x: rect.left - 5, // 左侧偏移5px
y: rect.top
}
} else {
this.changeTypeMenuPosition = {
x: rect.right + 5, // 右侧偏移5px
y: rect.top
}
}
}
this.showChangeType = true
// 阻止事件冒泡,防止触发外部点击事件
event?.stopPropagation()
},
handleChangeType (type) { // 保存原节点的位置和ID
this.changeNodeType(type) const { x, y, id } = this.node;
},
// 显示新增子节点菜单 // 创建新节点保持原有的位置和ID
handleShowAddChild (event) { const newNode = {
// 计算子菜单位置,在父菜单右侧显示 ...nodeConfig,
const menuEl = this.$el.querySelector('.more-menu') id,
if (menuEl) { x,
const rect = menuEl.getBoundingClientRect() y,
// 检查右侧空间是否足够 };
const rightSpace = window.innerWidth - rect.right
const leftSpace = rect.left
// 如果右侧空间不足,且左侧空间足够,则显示在左侧 // 更新节点列表
if (rightSpace < 200 && leftSpace > 200) { const newNodes = [...this.parent.nodes];
this.addChildMenuPosition = { newNodes[nodeIndex] = newNode;
x: rect.left - 5, // 左侧偏移5px this.parent.nodes = newNodes;
y: rect.top
}
} else {
this.addChildMenuPosition = {
x: rect.right + 5, // 右侧偏移5px
y: rect.top
}
}
}
this.showAddChild = true
// 阻止事件冒泡,防止触发外部点击事件
event?.stopPropagation()
},
// 处理新增子节点 // 关闭菜单
handleAddChild (type) { this.$emit('update:visible', false);
if (!this.node || !this.parent || typeof this.parent.addNode !== 'function') return this.showChangeType = false;
},
// 模拟右键添加节点的流程 handleShowChangeType(event) {
this.parent.contextMenuNode = this.node // 计算子菜单位置,在父菜单右侧显示
this.parent.contextMenuAddPosition = 'right' const menuEl = this.$el.querySelector('.more-menu');
this.parent.contextMenuPortIndex = 0 if (menuEl) {
const rect = menuEl.getBoundingClientRect();
// 检查右侧空间是否足够
const rightSpace = window.innerWidth - rect.right;
const leftSpace = rect.left;
// 调用父组件的 addNode 方法 // 如果右侧空间不足,且左侧空间足够,则显示在左侧
this.parent.addNode(type) if (rightSpace < 200 && leftSpace > 200) {
this.changeTypeMenuPosition = {
x: rect.left - 5, // 左侧偏移5px
y: rect.top,
};
} else {
this.changeTypeMenuPosition = {
x: rect.right + 5, // 右侧偏移5px
y: rect.top,
};
}
}
this.showChangeType = true;
// 阻止事件冒泡,防止触发外部点击事件
event?.stopPropagation();
},
// 关闭菜单 handleChangeType(type) {
this.$emit('update:visible', false) this.changeNodeType(type);
this.showAddChild = false },
}
},
mounted () {
// 点击外部关闭菜单
const handleClickOutside = (e) => {
if (!this.$el.contains(e.target)) {
this.$emit('update:visible', false)
this.showChangeType = false
this.showAddChild = false
}
}
document.addEventListener('click', handleClickOutside)
// 保存引用以便在组件销毁时移除 // 显示新增子节点菜单
this.handleClickOutside = handleClickOutside handleShowAddChild(event) {
}, // 计算子菜单位置,在父菜单右侧显示
beforeUnmount () { const menuEl = this.$el.querySelector('.more-menu');
// 移除事件监听器 if (menuEl) {
document.removeEventListener('click', this.handleClickOutside) const rect = menuEl.getBoundingClientRect();
} // 检查右侧空间是否足够
} const rightSpace = window.innerWidth - rect.right;
const leftSpace = rect.left;
// 如果右侧空间不足,且左侧空间足够,则显示在左侧
if (rightSpace < 200 && leftSpace > 200) {
this.addChildMenuPosition = {
x: rect.left - 5, // 左侧偏移5px
y: rect.top,
};
} else {
this.addChildMenuPosition = {
x: rect.right + 5, // 右侧偏移5px
y: rect.top,
};
}
}
this.showAddChild = true;
// 阻止事件冒泡,防止触发外部点击事件
event?.stopPropagation();
},
// 处理新增子节点
handleAddChild(type) {
if (!this.node || !this.parent || typeof this.parent.addNode !== 'function') return;
// 模拟右键添加节点的流程
this.parent.contextMenuNode = this.node;
this.parent.contextMenuAddPosition = 'right';
this.parent.contextMenuPortIndex = 0;
// 调用父组件的 addNode 方法
this.parent.addNode(type);
// 关闭菜单
this.$emit('update:visible', false);
this.showAddChild = false;
},
},
mounted() {
// 点击外部关闭菜单
const handleClickOutside = (e) => {
if (!this.$el.contains(e.target)) {
this.$emit('update:visible', false);
this.showChangeType = false;
this.showAddChild = false;
}
};
document.addEventListener('click', handleClickOutside);
// 保存引用以便在组件销毁时移除
this.handleClickOutside = handleClickOutside;
},
beforeUnmount() {
// 移除事件监听器
document.removeEventListener('click', this.handleClickOutside);
},
};
</script> </script>
<style scoped> <style scoped>
.more-menu { .more-menu {
opacity: 0.9; opacity: 0.9;
position: fixed; position: fixed;
background: white; background: white;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 4px 0; padding: 4px 0;
min-width: 160px; min-width: 160px;
z-index: 1000; z-index: 1000;
} }
.menu-item { .menu-item {
padding: 8px 16px; padding: 8px 16px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
cursor: pointer; cursor: pointer;
font-size: 13px; font-size: 13px;
color: rgb(71 84 103 / 1); color: rgb(71 84 103 / 1);
transition: all 0.2s; transition: all 0.2s;
position: relative; position: relative;
} }
.menu-item:hover { .menu-item:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
.el-icon-arrow-right { .el-icon-arrow-right {
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
} }
</style> </style>

View File

@@ -1,336 +1,329 @@
<template> <template>
<div class="workflow-designer"> <div class="workflow-designer">
<!-- 添加未发布遮罩组件 --> <!-- 添加未发布遮罩组件 -->
<UnpublishedMask :visible="!form.enabled" /> <UnpublishedMask :visible="!form.enabled" />
<!-- 执行面板 --> <!-- 执行面板 -->
<div class="execution-panel" <div class="execution-panel" v-if="form.enabled">
v-if="form.enabled"> <div class="panel-header">
<div class="panel-header"> <h3>流程运行{{ id }}</h3>
<h3>流程运行{{ id }}</h3> </div>
</div>
<div class="panel-content"> <div class="panel-content">
<!-- 参数输入区域 --> <!-- 参数输入区域 -->
<div class="left-panel"> <div class="left-panel">
<div class="variable-inputs"> <div class="variable-inputs">
<div v-for="(param, index) in startNodeParams" <div v-for="(param, index) in startNodeParams" :key="index" class="input-item">
:key="index" <div class="input-label" :class="{ required: param.required }">
class="input-item"> {{ param.name }}
<div class="input-label" </div>
:class="{ 'required': param.required }"> <div class="input-value">
{{ param.name }} <input
</div> v-if="param.inputType === 'input'"
<div class="input-value"> v-model="param.value"
<input v-if="param.inputType === 'input'" class="param-input"
v-model="param.value" :disabled="isRunning"
class="param-input" :class="{ error: showError && param.required && !param.value }"
:disabled="isRunning" :placeholder="'请输入' + param.name"
:class="{ 'error': showError && param.required && !param.value }" />
:placeholder="'请输入' + param.name" /> <input
<input v-else-if="param.inputType === 'number'" v-else-if="param.inputType === 'number'"
type="number" type="number"
v-model.number="param.value" v-model.number="param.value"
class="param-input" class="param-input"
:disabled="isRunning" :disabled="isRunning"
:class="{ 'error': showError && param.required && !param.value }" :class="{ error: showError && param.required && !param.value }"
:placeholder="'请输入' + param.name" /> :placeholder="'请输入' + param.name"
<textarea v-else-if="param.inputType === 'textarea'" />
v-model="param.value" <textarea
class="param-textarea" v-else-if="param.inputType === 'textarea'"
:disabled="isRunning" v-model="param.value"
:class="{ 'error': showError && param.required && !param.value }" class="param-textarea"
:placeholder="'请输入' + param.name" :disabled="isRunning"
rows="3"></textarea> :class="{ error: showError && param.required && !param.value }"
<select v-else-if="param.inputType === 'select'" :placeholder="'请输入' + param.name"
v-model="param.value" rows="3"
class="param-select" ></textarea>
:disabled="isRunning" <select
:class="{ 'error': showError && param.required && !param.value }"> v-else-if="param.inputType === 'select'"
<option value="">请选择{{ param.name }}</option> v-model="param.value"
<option v-for="option in param.options" class="param-select"
:key="option.value" :disabled="isRunning"
:value="option.value"> :class="{ error: showError && param.required && !param.value }"
{{ option.label }} >
</option> <option value="">请选择{{ param.name }}</option>
</select> <option v-for="option in param.options" :key="option.value" :value="option.value">
</div> {{ option.label }}
</div> </option>
</select>
</div>
</div>
<el-button type="primary" <el-button type="primary" class="run-btn" :disabled="isRunning" @click="handleParamRun">
class="run-btn" {{ isRunning ? '运行中...' : '运行' }}
:disabled="isRunning" </el-button>
@click="handleParamRun"> </div>
{{ isRunning ? '运行中...' : '运行' }}
</el-button>
</div>
<!-- 执行状态和结果区域 --> <!-- 执行状态和结果区域 -->
<div class="execution-detail" <div class="execution-detail" v-if="executionNodes.length">
v-if="executionNodes.length"> <div class="detail-card">
<div class="detail-card"> <div class="detail-row">
<div class="detail-row"> <div class="detail-item">
<div class="detail-item"> <div class="label">状态</div>
<div class="label">状态</div> <div class="value" :class="executionStatus.class">
<div class="value" {{ executionStatus.text }}
:class="executionStatus.class"> </div>
{{ executionStatus.text }} </div>
</div> <div class="detail-item">
</div> <div class="label">运行时间</div>
<div class="detail-item"> <div class="value">{{ formatTotalTime(executionTime) }}</div>
<div class="label">运行时间</div> </div>
<div class="value">{{ formatTotalTime(executionTime) }}</div> <div class="detail-item">
</div> <div class="label"> TOKEN </div>
<div class="detail-item"> <div class="value">{{ totalTokens }} Tokens</div>
<div class="label"> TOKEN </div> </div>
<div class="value">{{ totalTokens }} Tokens</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="right-panel">
</div> <!-- 执行进度和结果区域 -->
<div class="right-panel"> <node-list :nodes="executionNodes" @end="handleEnd" v-if="executionNodes.length" />
<!-- 执行进度和结果区域 -->
<node-list :nodes="executionNodes"
@end="handleEnd"
v-if="executionNodes.length" />
<!-- 最终执行结果 --> <!-- 最终执行结果 -->
<div class="execution-result" <div class="execution-result" v-if="executionResult">
v-if="executionResult"> <h4>执行结果</h4>
<h4>执行结果</h4> <pre>{{ JSON.stringify(executionResult, null, 2) }}</pre>
<pre>{{ JSON.stringify(executionResult, null, 2) }}</pre> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { Loading, Check, CircleClose, ArrowRight } from '@element-plus/icons-vue' import { Loading, Check, CircleClose, ArrowRight } from '@element-plus/icons-vue';
import { getObj } from '/@/api/knowledge/aiFlow'; import { getObj } from '/@/api/knowledge/aiFlow';
import NodeList from './components/NodeList.vue' import NodeList from './components/NodeList.vue';
import NodeCommon from './mixins/Node.ts' import NodeCommon from './mixins/Node.ts';
import UnpublishedMask from './components/UnpublishedMask.vue' import UnpublishedMask from './components/UnpublishedMask.vue';
export default { export default {
name: 'WorkflowRun', name: 'WorkflowRun',
mixins: [NodeCommon], mixins: [NodeCommon],
components: { components: {
Loading, Loading,
Check, Check,
CircleClose, CircleClose,
ArrowRight, ArrowRight,
NodeList, NodeList,
UnpublishedMask UnpublishedMask,
}, },
provide () { provide() {
return { return {
parent: this, parent: this,
nodes: this.nodes, nodes: this.nodes,
} };
}, },
data () { data() {
return { return {
form: { enabled: true }, form: { enabled: true },
executionNodes: [], executionNodes: [],
executionResult: null, executionResult: null,
executionTime: 0, executionTime: 0,
totalTokens: 0, totalTokens: 0,
startNodeParams: [], // 添加开始节点参数 startNodeParams: [], // 添加开始节点参数
showError: false, showError: false,
isRunning: false // 添加运行状态控制 isRunning: false, // 添加运行状态控制
} };
}, },
computed: { computed: {
executionStatus () { executionStatus() {
const lastNode = this.executionNodes[this.executionNodes.length - 1] const lastNode = this.executionNodes[this.executionNodes.length - 1];
if (!lastNode) return { text: '等待中', class: 'status-pending' } if (!lastNode) return { text: '等待中', class: 'status-pending' };
const statusMap = { const statusMap = {
'running': { text: '运行中', class: 'status-running' }, running: { text: '运行中', class: 'status-running' },
'success': { text: '成功', class: 'status-success' }, success: { text: '成功', class: 'status-success' },
'error': { text: '失败', class: 'status-error' }, error: { text: '失败', class: 'status-error' },
'skipped': { text: '已跳过', class: 'status-skipped' } skipped: { text: '已跳过', class: 'status-skipped' },
} };
return statusMap[lastNode.status] || { text: '等待中', class: 'status-pending' } return statusMap[lastNode.status] || { text: '等待中', class: 'status-pending' };
}, },
}, },
created () { created() {
this.loadFromStorage() this.loadFromStorage();
}, },
unmounted() { unmounted() {
this.resetConversation(); this.resetConversation();
}, },
methods: { methods: {
// 修改 loadFromStorage 方法 // 修改 loadFromStorage 方法
async loadFromStorage () { async loadFromStorage() {
try { try {
const res = await getObj(this.id) const res = await getObj(this.id);
this.form = res.data.data; this.form = res.data.data;
const { dsl = '{}' } = this.form const { dsl = '{}' } = this.form;
const data = JSON.parse(dsl) const data = JSON.parse(dsl);
this.nodes = data.nodes || [] this.nodes = data.nodes || [];
this.connections = data.connections || [] this.connections = data.connections || [];
this.env = data.env || [] this.env = data.env || [];
this.handleRunClick() this.handleRunClick();
} catch (error) { } catch (error) {
console.error('加载工作流失败:', error) console.error('加载工作流失败:', error);
} }
}, },
formatTotalTime (time) { formatTotalTime(time) {
if (!time) return '0ms' if (!time) return '0ms';
return `${Number(time).toFixed(3)}ms` return `${Number(time).toFixed(3)}ms`;
}, },
handleParamRun () { handleParamRun() {
const hasError = this.startNodeParams.some(param => param.required && !param.value) const hasError = this.startNodeParams.some((param) => param.required && !param.value);
this.showError = hasError this.showError = hasError;
if (hasError) { if (hasError) {
return return;
} }
this.runWorkflow(this.startNodeParams) this.runWorkflow(this.startNodeParams);
this.showError = false this.showError = false;
}, },
handleEnd (status) { handleEnd(status) {
this.isRunning = false this.isRunning = false;
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use './styles/flow.scss'; @use './styles/flow.scss';
.workflow-designer { .workflow-designer {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
background: #f8f9fc; background: #f8f9fc;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
} }
.execution-panel { .execution-panel {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: white; background: white;
border-left: 1px solid #e6e6e6; border-left: 1px solid #e6e6e6;
display: flex; display: flex;
color: #333; color: #333;
flex-direction: column; flex-direction: column;
} }
.panel-header { .panel-header {
padding: 15px; padding: 15px;
border-bottom: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
background: #f8f9fc; background: #f8f9fc;
h3 { h3 {
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
color: #303133; color: #303133;
} }
} }
.panel-content { .panel-content {
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.left-panel { .left-panel {
padding: 15px; padding: 15px;
width: 350px; width: 350px;
height: 100%; height: 100%;
background: #fff; background: #fff;
border-right: 1px solid #dcdfe6; border-right: 1px solid #dcdfe6;
} }
.right-panel { .right-panel {
flex: 1; flex: 1;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
padding: 20px; padding: 20px;
} }
.execution-detail { .execution-detail {
margin-top: 20px; margin-top: 20px;
} }
.detail-card { .detail-card {
background: #f0f9eb; background: #f0f9eb;
border-radius: 8px; border-radius: 8px;
border: 1px solid #e1f3d8; border: 1px solid #e1f3d8;
box-sizing: border-box; box-sizing: border-box;
} }
.detail-row { .detail-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.detail-item { .detail-item {
flex: 1; flex: 1;
text-align: center; text-align: center;
padding: 8px 0; padding: 8px 0;
&:not(:last-child) { &:not(:last-child) {
border-right: 1px solid rgba(225, 243, 216, 0.8); border-right: 1px solid rgba(225, 243, 216, 0.8);
} }
.label { .label {
color: #606266; color: #606266;
font-size: 12px; font-size: 12px;
margin-bottom: 4px; margin-bottom: 4px;
} }
.value { .value {
color: #67c23a; color: #67c23a;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
&.status-success { &.status-success {
color: #67c23a; color: #67c23a;
} }
&.status-error { &.status-error {
color: #f56c6c; color: #f56c6c;
} }
&.status-running { &.status-running {
color: #409eff; color: #409eff;
} }
&.status-pending { &.status-pending {
color: #909399; color: #909399;
} }
} }
} }
.run-btn { .run-btn {
width: 100%; width: 100%;
} }
.execution-result { .execution-result {
margin-top: 20px; margin-top: 20px;
h4 { h4 {
margin-bottom: 10px; margin-bottom: 10px;
color: #303133; color: #303133;
} }
pre { pre {
background: #f5f7fa; background: #f5f7fa;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
overflow-x: auto; overflow-x: auto;
font-family: monospace; font-family: monospace;
font-size: 13px; font-size: 13px;
line-height: 1.5; line-height: 1.5;
} }
} }
</style> </style>

View File

@@ -1,138 +1,129 @@
<template> <template>
<!-- 对话详情抽屉 --> <!-- 对话详情抽屉 -->
<el-drawer v-model="visible" <el-drawer v-model="visible" title="对话详情" size="500" :before-close="handleClose" class="chat-detail-drawer">
title="对话详情" <div v-loading="loading">
size="500" <ChatMessage :messages="messages" v-if="messages.length > 0" ref="messageContainer" />
:before-close="handleClose" <el-empty v-else description="暂无对话记录"> </el-empty>
class="chat-detail-drawer"> </div>
<div v-loading="loading"> </el-drawer>
<ChatMessage :messages="messages"
v-if="messages.length > 0"
ref="messageContainer" />
<el-empty v-else
description="暂无对话记录">
</el-empty>
</div>
</el-drawer>
</template> </template>
<script> <script>
import { marked } from 'marked' import { marked } from 'marked';
import { Cpu } from '@element-plus/icons-vue' import { Cpu } from '@element-plus/icons-vue';
import ChatMessage from './ChatMessage.vue' import ChatMessage from './ChatMessage.vue';
export default { export default {
name: 'ChatDetail', name: 'ChatDetail',
components: { components: {
Cpu, Cpu,
ChatMessage ChatMessage,
}, },
props: { props: {
// 对话ID // 对话ID
conversationId: { conversationId: {
type: [String, Number], type: [String, Number],
default: '' default: '',
}, },
// 是否显示弹窗 // 是否显示弹窗
modelValue: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
data () { data() {
return { return {
loading: false, loading: false,
messages: [] messages: [],
} };
}, },
computed: { computed: {
visible: { visible: {
get () { get() {
return this.modelValue return this.modelValue;
}, },
set (val) { set(val) {
this.$emit('update:modelValue', val) this.$emit('update:modelValue', val);
} },
} },
}, },
watch: { watch: {
conversationId: { conversationId: {
handler (val) { handler(val) {
if (val) { if (val) {
this.loadMessages() this.loadMessages();
} }
}, },
immediate: true immediate: true,
} },
}, },
methods: { methods: {
// 加载消息列表 // 加载消息列表
async loadMessages () { async loadMessages() {
if (!this.conversationId) return if (!this.conversationId) return;
this.loading = true this.loading = true;
try { try {
const res = null const res = null;
this.messages = res.data.data.records || [] this.messages = res.data.data.records || [];
} catch (error) { } catch (error) {
console.error('加载消息失败:', error) console.error('加载消息失败:', error);
this.$message.error('加载消息失败') this.$message.error('加载消息失败');
} finally { } finally {
this.loading = false this.loading = false;
} }
}, },
// 解析 Markdown // 解析 Markdown
parseMarkdown (text) { parseMarkdown(text) {
if (!text) return '' if (!text) return '';
return marked(text) return marked(text);
}, },
// 关闭弹窗 // 关闭弹窗
handleClose () { handleClose() {
this.visible = false this.visible = false;
}, },
/** /**
* 处理消息编辑事件 * 处理消息编辑事件
* @param {Object} data - 包含索引和更新后消息的对象 * @param {Object} data - 包含索引和更新后消息的对象
*/ */
handleMessageEdited(data) { handleMessageEdited(data) {
// 更新消息数组中的消息 // 更新消息数组中的消息
if (data && data.index >= 0 && data.index < this.messages.length) { if (data && data.index >= 0 && data.index < this.messages.length) {
this.messages[data.index] = data.message; this.messages[data.index] = data.message;
// 发送更新事件给父组件 // 发送更新事件给父组件
this.$emit('message-updated', this.messages); this.$emit('message-updated', this.messages);
} }
}, },
/** /**
* 处理消息删除事件 * 处理消息删除事件
* @param {Number} index - 要删除的消息索引 * @param {Number} index - 要删除的消息索引
*/ */
handleMessageDeleted(index) { handleMessageDeleted(index) {
// 从消息数组中删除消息 // 从消息数组中删除消息
if (index >= 0 && index < this.messages.length) { if (index >= 0 && index < this.messages.length) {
this.messages.splice(index, 1); this.messages.splice(index, 1);
// 发送更新事件给父组件 // 发送更新事件给父组件
this.$emit('message-updated', this.messages); this.$emit('message-updated', this.messages);
} }
} },
} },
} };
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-detail-drawer { .chat-detail-drawer {
.el-drawer__body{ .el-drawer__body {
padding: 0; padding: 0;
} }
} }
</style> </style>

View File

@@ -1,222 +1,189 @@
<template> <template>
<div class="messages"> <div class="messages">
<div v-for="(msg, index) in messages" <div v-for="(msg, index) in messages" :key="index" class="message" :class="[msg.role]" @contextmenu.prevent="showContextMenu($event, msg, index)">
:key="index" <div class="avatar">
class="message" <img :src="msg.role === 'user' ? currentUserAvatar : botAvatar" alt="avatar" />
:class="[msg.role]" </div>
@contextmenu.prevent="showContextMenu($event, msg, index)"> <div style="width: 100%">
<div class="avatar"> <!-- 消息时间显示 - 移到content外面 -->
<img :src="msg.role === 'user' ? currentUserAvatar : botAvatar" <div class="time">{{ getMessageTime(msg) }}</div>
alt="avatar" /> <div class="content">
</div> <!-- 思考内容区域添加可折叠功能默认展开 -->
<div style="width: 100%;"> <div class="collapsible_wrapper" v-if="msg.reasoning_content">
<!-- 消息时间显示 - 移到content外面 --> <div class="collapsible_tag" @click="toggleContent(index, 'reasoning')">
<div class="time">{{ getMessageTime(msg) }}</div> <el-icon class="collapsible-icon" :class="{ 'is-rotate': contentVisible[index]?.reasoning !== false }">
<div class="content"> <ArrowDown />
<!-- 思考内容区域添加可折叠功能默认展开 --> </el-icon>
<div class="collapsible_wrapper" <span>深度思考</span>
v-if="msg.reasoning_content"> </div>
<div class="collapsible_tag" <div class="collapsible_content" v-show="contentVisible[index]?.reasoning !== false">
@click="toggleContent(index, 'reasoning')"> {{ msg.reasoning_content }}
<el-icon class="collapsible-icon" </div>
:class="{ 'is-rotate': contentVisible[index]?.reasoning !== false }"> </div>
<ArrowDown /> <template v-if="msg.content">
</el-icon> <div v-html="parseMarkdown(msg.content)"></div>
<span>深度思考</span> <div class="questions" v-if="msg.questions">
</div> <el-tag v-for="(question, qIndex) in msg.questions" :key="qIndex" type="primary" @click="handleQuestionClick(question.text)">
<div class="collapsible_content" {{ question.text }}
v-show="contentVisible[index]?.reasoning !== false"> </el-tag>
{{ msg.reasoning_content }} </div>
</div> </template>
</div> <div v-else-if="msg.nodes" class="collapsible_wrapper">
<template v-if="msg.content"> <div class="collapsible_tag" @click="toggleContent(index, 'nodes')">
<div v-html="parseMarkdown(msg.content)"></div> <el-icon class="collapsible-icon" :class="{ 'is-rotate': contentVisible[index]?.nodes !== false }">
<div class="questions" <ArrowDown />
v-if="msg.questions"> </el-icon>
<el-tag v-for="(question, qIndex) in msg.questions" <span>执行步骤</span>
:key="qIndex" </div>
type="primary" <div class="collapsible_content" v-show="contentVisible[index]?.nodes !== false">
@click="handleQuestionClick(question.text)"> <node-list :nodes="msg.nodes" />
{{ question.text }} </div>
</el-tag> <div v-if="msg.result" v-html="parseMarkdown(msg.result)"></div>
</div> </div>
</template> <template v-if="loading && messages.indexOf(msg) === messages.length - 1">
<div v-else-if="msg.nodes" <div class="typing-indicator">
class="collapsible_wrapper"> <div class="dot"></div>
<div class="collapsible_tag" <div class="dot"></div>
@click="toggleContent(index, 'nodes')"> <div class="dot"></div>
<el-icon class="collapsible-icon" </div>
:class="{ 'is-rotate': contentVisible[index]?.nodes !== false }"> </template>
<ArrowDown /> </div>
</el-icon> </div>
<span>执行步骤</span> </div>
</div> <!-- 右键菜单 -->
<div class="collapsible_content" <el-dialog v-model="editDialogVisible" title="编辑消息" width="50%" :before-close="handleEditDialogClose">
v-show="contentVisible[index]?.nodes !== false"> <el-input v-model="editingContent" type="textarea" :rows="10" placeholder="请输入消息内容" />
<node-list :nodes="msg.nodes" /> <template #footer>
</div> <span class="dialog-footer">
<div v-if="msg.result" <el-button @click="handleEditDialogClose">取消</el-button>
v-html="parseMarkdown(msg.result)"> <el-button type="primary" @click="saveEditedMessage">确认</el-button>
</div> </span>
</div> </template>
<template v-if="loading && messages.indexOf(msg) === messages.length - 1"> </el-dialog>
<div class="typing-indicator">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</template>
</div>
</div>
</div>
<!-- 右键菜单 -->
<el-dialog v-model="editDialogVisible"
title="编辑消息"
width="50%"
:before-close="handleEditDialogClose">
<el-input v-model="editingContent"
type="textarea"
:rows="10"
placeholder="请输入消息内容" />
<template #footer>
<span class="dialog-footer">
<el-button @click="handleEditDialogClose">取消</el-button>
<el-button type="primary"
@click="saveEditedMessage">确认</el-button>
</span>
</template>
</el-dialog>
<!-- 自定义右键菜单 -->
<div v-show="contextMenuVisible"
class="context-menu"
:style="{ top: contextMenuTop + 'px', left: contextMenuLeft + 'px' }">
<div class="context-menu-item"
@click="copyMessage">
<el-icon>
<Document />
</el-icon>
<span>复制</span>
</div>
<div class="context-menu-item"
@click="editMessage">
<el-icon>
<Edit />
</el-icon>
<span>编辑</span>
</div>
<div class="context-menu-item"
@click="deleteMessage">
<el-icon>
<Delete />
</el-icon>
<span>删除</span>
</div>
<div class="context-menu-item"
@click="speakMessage">
<el-icon>
<Microphone />
</el-icon>
<span>朗读</span>
</div>
</div>
</div>
<!-- 自定义右键菜单 -->
<div v-show="contextMenuVisible" class="context-menu" :style="{ top: contextMenuTop + 'px', left: contextMenuLeft + 'px' }">
<div class="context-menu-item" @click="copyMessage">
<el-icon>
<Document />
</el-icon>
<span>复制</span>
</div>
<div class="context-menu-item" @click="editMessage">
<el-icon>
<Edit />
</el-icon>
<span>编辑</span>
</div>
<div class="context-menu-item" @click="deleteMessage">
<el-icon>
<Delete />
</el-icon>
<span>删除</span>
</div>
<div class="context-menu-item" @click="speakMessage">
<el-icon>
<Microphone />
</el-icon>
<span>朗读</span>
</div>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue' import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { marked } from 'marked' import { marked } from 'marked';
import hljs from 'highlight.js/lib/core' import hljs from 'highlight.js/lib/core';
// 按需导入常用的语言 // 按需导入常用的语言
import javascript from 'highlight.js/lib/languages/javascript' import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript' import typescript from 'highlight.js/lib/languages/typescript';
import python from 'highlight.js/lib/languages/python' import python from 'highlight.js/lib/languages/python';
import java from 'highlight.js/lib/languages/java' import java from 'highlight.js/lib/languages/java';
import xml from 'highlight.js/lib/languages/xml' import xml from 'highlight.js/lib/languages/xml';
import css from 'highlight.js/lib/languages/css' import css from 'highlight.js/lib/languages/css';
import scss from 'highlight.js/lib/languages/scss' import scss from 'highlight.js/lib/languages/scss';
import json from 'highlight.js/lib/languages/json' import json from 'highlight.js/lib/languages/json';
import bash from 'highlight.js/lib/languages/bash' import bash from 'highlight.js/lib/languages/bash';
import markdown from 'highlight.js/lib/languages/markdown' import markdown from 'highlight.js/lib/languages/markdown';
// 导入暗色主题样式 // 导入暗色主题样式
import 'highlight.js/styles/atom-one-dark.css' import 'highlight.js/styles/atom-one-dark.css';
// 导入Element Plus图标 // 导入Element Plus图标
import { ArrowDown, Document, Edit, Delete, Microphone } from '@element-plus/icons-vue' import { ArrowDown, Document, Edit, Delete, Microphone } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus';
import NodeList from '/@/views/knowledge/aiFlow/components/NodeList.vue' import NodeList from '/@/views/knowledge/aiFlow/components/NodeList.vue';
import {dateTimeStr} from "/@/utils/formatTime"; import { dateTimeStr } from '/@/utils/formatTime';
// 定义组件接收的props // 定义组件接收的props
const props = defineProps({ const props = defineProps({
loading: { loading: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
messages: { messages: {
type: Array, type: Array,
required: true required: true,
}, },
currentUserAvatar: { currentUserAvatar: {
type: String, type: String,
default: '/img/chat/icon.png' default: '/img/chat/icon.png',
}, },
botAvatar: { botAvatar: {
type: String, type: String,
default: '/img/chat/chatgpt.png' default: '/img/chat/chatgpt.png',
} },
}) });
// 定义组件触发的事件 // 定义组件触发的事件
const emit = defineEmits(['item-click', 'change']) const emit = defineEmits(['item-click', 'change']);
// 注册语言 // 注册语言
hljs.registerLanguage('javascript', javascript) hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript) hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('python', python) hljs.registerLanguage('python', python);
hljs.registerLanguage('java', java) hljs.registerLanguage('java', java);
hljs.registerLanguage('xml', xml) hljs.registerLanguage('xml', xml);
hljs.registerLanguage('css', css) hljs.registerLanguage('css', css);
hljs.registerLanguage('scss', scss) hljs.registerLanguage('scss', scss);
hljs.registerLanguage('json', json) hljs.registerLanguage('json', json);
hljs.registerLanguage('bash', bash) hljs.registerLanguage('bash', bash);
hljs.registerLanguage('markdown', markdown) hljs.registerLanguage('markdown', markdown);
// 配置marked的代码高亮选项 // 配置marked的代码高亮选项
onMounted(() => { onMounted(() => {
marked.setOptions({ marked.setOptions({
highlight: function (code, lang) { highlight: function (code, lang) {
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
return hljs.highlight(code, { language: lang }).value return hljs.highlight(code, { language: lang }).value;
} catch (e) { } catch (e) {
console.error(e) console.error(e);
return code return code;
} }
} }
return hljs.highlightAuto(code).value return hljs.highlightAuto(code).value;
}, },
breaks: true, // 支持换行符 breaks: true, // 支持换行符
gfm: true, // 启用GitHub风格Markdown gfm: true, // 启用GitHub风格Markdown
sanitize: false, // 允许HTML标签以支持代码高亮 sanitize: false, // 允许HTML标签以支持代码高亮
langPrefix: 'hljs language-', // 添加代码块的class前缀 langPrefix: 'hljs language-', // 添加代码块的class前缀
}) });
}) });
// 使用reactive创建响应式对象用于跟踪内容的显示状态 // 使用reactive创建响应式对象用于跟踪内容的显示状态
const contentVisible = reactive({}) const contentVisible = reactive({});
// 右键菜单相关状态 // 右键菜单相关状态
const contextMenuVisible = ref(false) const contextMenuVisible = ref(false);
const contextMenuTop = ref(0) const contextMenuTop = ref(0);
const contextMenuLeft = ref(0) const contextMenuLeft = ref(0);
const currentMessage = ref(null) const currentMessage = ref(null);
const currentMessageIndex = ref(-1) const currentMessageIndex = ref(-1);
// 编辑对话框相关状态 // 编辑对话框相关状态
const editDialogVisible = ref(false) const editDialogVisible = ref(false);
const editingContent = ref('') const editingContent = ref('');
/** /**
* 切换内容的显示/隐藏状态 * 切换内容的显示/隐藏状态
@@ -224,11 +191,11 @@ const editingContent = ref('')
* @param {string} type - 内容类型 ('reasoning' 或 'nodes') * @param {string} type - 内容类型 ('reasoning' 或 'nodes')
*/ */
const toggleContent = (index, type) => { const toggleContent = (index, type) => {
if (!contentVisible[index]) { if (!contentVisible[index]) {
contentVisible[index] = {} contentVisible[index] = {};
} }
contentVisible[index][type] = contentVisible[index][type] === false ? true : false contentVisible[index][type] = contentVisible[index][type] === false ? true : false;
} };
/** /**
* 获取消息的时间 * 获取消息的时间
@@ -236,17 +203,16 @@ const toggleContent = (index, type) => {
* @returns {string} - 格式化后的时间字符串 * @returns {string} - 格式化后的时间字符串
*/ */
const getMessageTime = (msg) => { const getMessageTime = (msg) => {
// 如果消息对象中已有时间属性,则直接使用 // 如果消息对象中已有时间属性,则直接使用
if (msg.time) { if (msg.time) {
return parseDate(msg.time, dateTimeStr);
return parseDate(msg.time,dateTimeStr) }
} // 否则生成当前时间并赋值给消息对象
// 否则生成当前时间并赋值给消息对象 const currentTime = parseDate(new Date(), dateTimeStr);
const currentTime = parseDate(new Date(),dateTimeStr) // 为消息对象添加时间属性
// 为消息对象添加时间属性 msg.time = new Date().toISOString();
msg.time = new Date().toISOString() return currentTime;
return currentTime };
}
/** /**
* 显示右键菜单 * 显示右键菜单
@@ -255,153 +221,148 @@ const getMessageTime = (msg) => {
* @param {number} index - 消息索引 * @param {number} index - 消息索引
*/ */
const showContextMenu = (event, msg, index) => { const showContextMenu = (event, msg, index) => {
// 阻止默认右键菜单 // 阻止默认右键菜单
event.preventDefault() event.preventDefault();
// 设置菜单位置 // 设置菜单位置
contextMenuTop.value = event.clientY contextMenuTop.value = event.clientY;
contextMenuLeft.value = event.clientX contextMenuLeft.value = event.clientX;
// 保存当前消息和索引 // 保存当前消息和索引
currentMessage.value = msg currentMessage.value = msg;
currentMessageIndex.value = index currentMessageIndex.value = index;
// 显示菜单 // 显示菜单
contextMenuVisible.value = true contextMenuVisible.value = true;
} };
/** /**
* 隐藏右键菜单 * 隐藏右键菜单
*/ */
const hideContextMenu = () => { const hideContextMenu = () => {
contextMenuVisible.value = false contextMenuVisible.value = false;
} };
/** /**
* 复制消息内容到剪贴板 * 复制消息内容到剪贴板
*/ */
const copyMessage = () => { const copyMessage = () => {
if (!currentMessage.value) return if (!currentMessage.value) return;
// 获取要复制的文本内容 // 获取要复制的文本内容
const textToCopy = currentMessage.value.content || const textToCopy = currentMessage.value.content || currentMessage.value.reasoning_content || currentMessage.value.result || '';
currentMessage.value.reasoning_content ||
(currentMessage.value.result || '')
// 使用Clipboard API复制文本 // 使用Clipboard API复制文本
navigator.clipboard.writeText(textToCopy) navigator.clipboard
.then(() => { .writeText(textToCopy)
ElMessage.success('复制成功') .then(() => {
}) ElMessage.success('复制成功');
.catch(err => { })
ElMessage.error('复制失败: ' + err) .catch((err) => {
}) ElMessage.error('复制失败: ' + err);
});
// 隐藏菜单 // 隐藏菜单
hideContextMenu() hideContextMenu();
} };
/** /**
* 打开编辑对话框 * 打开编辑对话框
*/ */
const editMessage = () => { const editMessage = () => {
if (!currentMessage.value) return if (!currentMessage.value) return;
// 设置编辑内容 // 设置编辑内容
editingContent.value = currentMessage.value.content || editingContent.value = currentMessage.value.content || currentMessage.value.reasoning_content || currentMessage.value.result || '';
currentMessage.value.reasoning_content ||
(currentMessage.value.result || '')
// 显示编辑对话框 // 显示编辑对话框
editDialogVisible.value = true editDialogVisible.value = true;
// 隐藏菜单 // 隐藏菜单
hideContextMenu() hideContextMenu();
} };
/** /**
* 关闭编辑对话框 * 关闭编辑对话框
*/ */
const handleEditDialogClose = () => { const handleEditDialogClose = () => {
editDialogVisible.value = false editDialogVisible.value = false;
editingContent.value = '' editingContent.value = '';
} };
/** /**
* 保存编辑后的消息 * 保存编辑后的消息
*/ */
const saveEditedMessage = () => { const saveEditedMessage = () => {
if (currentMessageIndex.value === -1 || !currentMessage.value) return if (currentMessageIndex.value === -1 || !currentMessage.value) return;
// 更新时间 // 更新时间
currentMessage.value.time = dayjs().toISOString() currentMessage.value.time = dayjs().toISOString();
// 更新消息内容 // 更新消息内容
currentMessage.value.content = editingContent.value currentMessage.value.content = editingContent.value;
// 发送编辑消息事件 // 发送编辑消息事件
emit('change') emit('change');
// 关闭对话框 // 关闭对话框
handleEditDialogClose() handleEditDialogClose();
ElMessage.success('消息已更新') ElMessage.success('消息已更新');
} };
/** /**
* 删除消息 * 删除消息
*/ */
const deleteMessage = () => { const deleteMessage = () => {
if (currentMessageIndex.value === -1) return if (currentMessageIndex.value === -1) return;
// 从消息数组中删除当前消息 // 从消息数组中删除当前消息
props.messages.splice(currentMessageIndex.value, 1) props.messages.splice(currentMessageIndex.value, 1);
// 发送删除消息事件 // 发送删除消息事件
emit('change') emit('change');
// 隐藏菜单 // 隐藏菜单
hideContextMenu() hideContextMenu();
ElMessage.success('消息已删除') ElMessage.success('消息已删除');
} };
/** /**
* 使用浏览器API朗读消息 * 使用浏览器API朗读消息
*/ */
const speakMessage = () => { const speakMessage = () => {
if (!currentMessage.value) return if (!currentMessage.value) return;
// 获取要朗读的文本 // 获取要朗读的文本
const textToSpeak = currentMessage.value.content || const textToSpeak = currentMessage.value.content || currentMessage.value.reasoning_content || currentMessage.value.result || '';
currentMessage.value.reasoning_content ||
(currentMessage.value.result || '')
// 检查浏览器是否支持语音合成 // 检查浏览器是否支持语音合成
if ('speechSynthesis' in window) { if ('speechSynthesis' in window) {
// 创建语音合成实例 // 创建语音合成实例
const utterance = new SpeechSynthesisUtterance(textToSpeak) const utterance = new SpeechSynthesisUtterance(textToSpeak);
// 设置语音属性 // 设置语音属性
utterance.lang = 'zh-CN' // 设置语言为中文 utterance.lang = 'zh-CN'; // 设置语言为中文
utterance.rate = 1.0 // 设置语速 utterance.rate = 1.0; // 设置语速
utterance.pitch = 1.0 // 设置音调 utterance.pitch = 1.0; // 设置音调
// 开始朗读 // 开始朗读
window.speechSynthesis.speak(utterance) window.speechSynthesis.speak(utterance);
ElMessage.success('正在朗读消息') ElMessage.success('正在朗读消息');
} else { } else {
ElMessage.error('您的浏览器不支持语音合成') ElMessage.error('您的浏览器不支持语音合成');
} }
// 隐藏菜单 // 隐藏菜单
hideContextMenu() hideContextMenu();
} };
/** /**
* 处理问题点击事件 * 处理问题点击事件
* @param {string} text - 问题文本 * @param {string} text - 问题文本
*/ */
const handleQuestionClick = (text) => { const handleQuestionClick = (text) => {
emit('item-click', text) emit('item-click', text);
} };
/** /**
* 解析Markdown内容并支持代码高亮 * 解析Markdown内容并支持代码高亮
@@ -409,162 +370,161 @@ const handleQuestionClick = (text) => {
* @returns {string} - 解析后的HTML字符串 * @returns {string} - 解析后的HTML字符串
*/ */
const parseMarkdown = (content) => { const parseMarkdown = (content) => {
if (!content) return '' if (!content) return '';
return marked(content) return marked(content);
} };
// 点击页面其他区域时隐藏右键菜单 // 点击页面其他区域时隐藏右键菜单
const handleDocumentClick = () => { const handleDocumentClick = () => {
hideContextMenu() hideContextMenu();
} };
// 组件挂载时添加全局点击事件监听 // 组件挂载时添加全局点击事件监听
onMounted(() => { onMounted(() => {
document.addEventListener('click', handleDocumentClick) document.addEventListener('click', handleDocumentClick);
}) });
// 组件卸载前移除全局点击事件监听 // 组件卸载前移除全局点击事件监听
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.removeEventListener('click', handleDocumentClick) document.removeEventListener('click', handleDocumentClick);
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// 可折叠内容区域通用样式 // 可折叠内容区域通用样式
.collapsible_wrapper { .collapsible_wrapper {
overflow: hidden; overflow: hidden;
} }
.collapsible_tag { .collapsible_tag {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
padding: 0px 8px; padding: 0px 8px;
background-color: #f0f0f0; background-color: #f0f0f0;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
transition: all 0.2s; transition: all 0.2s;
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
margin-bottom: 6px; margin-bottom: 6px;
&:hover { &:hover {
background-color: #e8e8e8; background-color: #e8e8e8;
border-color: #d0d0d0; border-color: #d0d0d0;
} }
.collapsible-icon { .collapsible-icon {
margin-right: 6px; margin-right: 6px;
transition: transform 0.3s; transition: transform 0.3s;
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
&.is-rotate { &.is-rotate {
transform: rotate(180deg); transform: rotate(180deg);
} }
} }
span { span {
font-size: 12px; font-size: 12px;
color: #606266; color: #606266;
font-weight: 500; font-weight: 500;
} }
} }
.collapsible_content { .collapsible_content {
position: relative; position: relative;
margin: 5px 0; margin: 5px 0;
padding: 0px 12px; padding: 0px 12px;
box-sizing: border-box; box-sizing: border-box;
font-size: 13px; font-size: 13px;
&:before { &:before {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
content: ' '; content: ' ';
height: 100%; height: 100%;
width: 2px; width: 2px;
background-color: #e5e5e5; background-color: #e5e5e5;
} }
} }
// 右键菜单样式 // 右键菜单样式
.context-menu { .context-menu {
position: fixed; position: fixed;
z-index: 9999; z-index: 9999;
background: white; background: white;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 5px 0; padding: 5px 0;
min-width: 120px; min-width: 120px;
} }
.context-menu-item { .context-menu-item {
padding: 8px 16px; padding: 8px 16px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
transition: background-color 0.3s; transition: background-color 0.3s;
&:hover { &:hover {
background-color: #f5f7fa; background-color: #f5f7fa;
} }
.el-icon { .el-icon {
margin-right: 8px; margin-right: 8px;
font-size: 16px; font-size: 16px;
} }
span { span {
font-size: 14px; font-size: 14px;
} }
} }
:deep(pre) { :deep(pre) {
background-color: #282c34; // Atom One Dark 背景色 background-color: #282c34; // Atom One Dark 背景色
border-radius: 6px; border-radius: 6px;
padding: 16px; padding: 16px;
overflow: auto; overflow: auto;
font-size: 14px; font-size: 14px;
line-height: 1.45; line-height: 1.45;
margin-bottom: 16px; margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
} }
:deep(code) { :deep(code) {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
margin: 0; margin: 0;
font-size: 85%; font-size: 85%;
background-color: #3a404b; // Atom One Dark 次要背景色 background-color: #3a404b; // Atom One Dark 次要背景色
color: #abb2bf; // Atom One Dark 文本颜色 color: #abb2bf; // Atom One Dark 文本颜色
border-radius: 3px; border-radius: 3px;
} }
:deep(pre code) { :deep(pre code) {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-size: 100%; font-size: 100%;
word-break: normal; word-break: normal;
white-space: pre; white-space: pre;
background: transparent; background: transparent;
border: 0; border: 0;
color: #abb2bf; // Atom One Dark 文本颜色 color: #abb2bf; // Atom One Dark 文本颜色
} }
// 添加代码块的滚动条样式 // 添加代码块的滚动条样式
:deep(pre)::-webkit-scrollbar { :deep(pre)::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
:deep(pre)::-webkit-scrollbar-thumb { :deep(pre)::-webkit-scrollbar-thumb {
background: #3a404b; background: #3a404b;
border-radius: 4px; border-radius: 4px;
} }
:deep(pre)::-webkit-scrollbar-track { :deep(pre)::-webkit-scrollbar-track {
background: #282c34; background: #282c34;
border-radius: 4px; border-radius: 4px;
} }
</style> </style>

View File

@@ -13,7 +13,7 @@
:start-params="startNodeParams" :start-params="startNodeParams"
:conversation-id="conversationId" :conversation-id="conversationId"
@run="runWorkflow" @run="runWorkflow"
@update:stream="(value) => isStream = value" @update:stream="(value) => (isStream = value)"
@close="showExecutionPanel = false" @close="showExecutionPanel = false"
/> />
</template> </template>
@@ -302,7 +302,7 @@ export default {
if (sourceNode) { if (sourceNode) {
// 获取源节点已有的连接数量 // 获取源节点已有的连接数量
const existingConnections = this.connections.filter((conn) => conn.sourceId === params.sourceId); const existingConnections = this.connections.filter((conn) => conn.sourceId === params.sourceId);
// 如果不是分支节点switch 或 question且已经有连接则不允许创建新连接 // 如果不是分支节点switch 或 question且已经有连接则不允许创建新连接
if (!['switch', 'question'].includes(sourceNode.type) && existingConnections.length > 0) { if (!['switch', 'question'].includes(sourceNode.type) && existingConnections.length > 0) {
this.$message.warning('除分支节点外,每个节点只能有一个子节点'); this.$message.warning('除分支节点外,每个节点只能有一个子节点');
@@ -462,7 +462,7 @@ export default {
if (this.contextMenuAddPosition === 'right') { if (this.contextMenuAddPosition === 'right') {
// 获取当前节点已有的连接数量 // 获取当前节点已有的连接数量
const existingConnections = this.connections.filter((conn) => conn.sourceId === this.contextMenuNode.id); const existingConnections = this.connections.filter((conn) => conn.sourceId === this.contextMenuNode.id);
// 如果不是分支节点且已经有连接,则不允许添加 // 如果不是分支节点且已经有连接,则不允许添加
if (!['switch', 'question'].includes(this.contextMenuNode.type) && existingConnections.length > 0) { if (!['switch', 'question'].includes(this.contextMenuNode.type) && existingConnections.length > 0) {
this.$message.warning('除分支节点外,每个节点只能有一个子节点'); this.$message.warning('除分支节点外,每个节点只能有一个子节点');

View File

@@ -74,7 +74,6 @@
<div class="w-px h-4 mx-2 bg-gray-200 dark:bg-gray-700"></div> <div class="w-px h-4 mx-2 bg-gray-200 dark:bg-gray-700"></div>
<el-button <el-button
class="!p-2 text-gray-600 rounded-full transition-colors dark:text-gray-300 hover:text-primary hover:bg-gray-100 dark:hover:bg-gray-700" class="!p-2 text-gray-600 rounded-full transition-colors dark:text-gray-300 hover:text-primary hover:bg-gray-100 dark:hover:bg-gray-700"
text text

View File

@@ -246,7 +246,7 @@ export default {
}, },
onProgress: (event: FlowExecutionEvent) => { onProgress: (event: FlowExecutionEvent) => {
this.$log?.info?.(`节点执行进度: ${event.nodeName} (${event.nodeId})`); this.$log?.info?.(`节点执行进度: ${event.nodeName} (${event.nodeId})`);
// 更新执行节点状态 // 更新执行节点状态
if (event.nodeId && this.executionNodes) { if (event.nodeId && this.executionNodes) {
const nodeIndex = this.executionNodes.findIndex((n: ExecutionNode) => n.id === event.nodeId); const nodeIndex = this.executionNodes.findIndex((n: ExecutionNode) => n.id === event.nodeId);
@@ -255,12 +255,12 @@ export default {
this.executionNodes[nodeIndex] = { this.executionNodes[nodeIndex] = {
...this.executionNodes[nodeIndex], ...this.executionNodes[nodeIndex],
status: 'running', status: 'running',
...event.data ...event.data,
}; };
}); });
} }
} }
// 可以在这里添加进度条或其他UI更新 // 可以在这里添加进度条或其他UI更新
if (event.progress !== undefined) { if (event.progress !== undefined) {
this.$log?.info?.(`执行进度: ${event.progress}%`); this.$log?.info?.(`执行进度: ${event.progress}%`);
@@ -270,7 +270,7 @@ export default {
// 累积聊天消息内容 // 累积聊天消息内容
chatMessageContent += content; chatMessageContent += content;
isChatStreaming = true; isChatStreaming = true;
// 处理 tokens、duration 和 nodes 信息 // 处理 tokens、duration 和 nodes 信息
if (isComplete) { if (isComplete) {
if (tokens) { if (tokens) {
@@ -283,19 +283,19 @@ export default {
this.executionNodes = nodes; this.executionNodes = nodes;
} }
} }
// 实时更新executionResult以显示聊天消息 // 实时更新executionResult以显示聊天消息
this.$nextTick(() => { this.$nextTick(() => {
this.executionResult = { this.executionResult = {
...this.executionResult, ...this.executionResult,
chatMessage: chatMessageContent, chatMessage: chatMessageContent,
isStreaming: !isComplete isStreaming: !isComplete,
}; };
}); });
}, },
onComplete: (result: FlowExecutionResult) => { onComplete: (result: FlowExecutionResult) => {
this.$log?.info?.('工作流执行完成'); this.$log?.info?.('工作流执行完成');
this.$nextTick(() => { this.$nextTick(() => {
this.executionNodes = result.nodes; this.executionNodes = result.nodes;
// 如果有聊天消息,将其合并到结果中 // 如果有聊天消息,将其合并到结果中
@@ -303,7 +303,7 @@ export default {
this.executionResult = { this.executionResult = {
...result.result, ...result.result,
chatMessage: chatMessageContent, chatMessage: chatMessageContent,
isStreaming: false isStreaming: false,
}; };
} else { } else {
this.executionResult = result.result; this.executionResult = result.result;
@@ -315,7 +315,7 @@ export default {
}, },
onError: (error: string) => { onError: (error: string) => {
this.$log?.error?.('工作流执行失败:', error); this.$log?.error?.('工作流执行失败:', error);
this.$nextTick(() => { this.$nextTick(() => {
// 如果有部分聊天消息,也保留在错误结果中 // 如果有部分聊天消息,也保留在错误结果中
const errorResult: any = { error }; const errorResult: any = { error };
@@ -339,7 +339,7 @@ export default {
this.executionResult = { this.executionResult = {
chatMessage: chatResult.chatMessage, chatMessage: chatResult.chatMessage,
result: chatResult.result, result: chatResult.result,
isStreaming: false isStreaming: false,
}; };
this.isRunning = false; this.isRunning = false;
}); });
@@ -350,11 +350,11 @@ export default {
* @param {ExecutionContext} context - 执行上下文 * @param {ExecutionContext} context - 执行上下文
*/ */
async executeWithHTTP(this: any, context: ExecutionContext): Promise<void> { async executeWithHTTP(this: any, context: ExecutionContext): Promise<void> {
const { data } = await executeFlow({ const { data } = await executeFlow({
id: this.id, id: this.id,
params: context.params, params: context.params,
envs: context.envs, envs: context.envs,
stream: false stream: false,
}); });
// 处理普通JSON响应 // 处理普通JSON响应
@@ -389,7 +389,7 @@ export default {
this.executionResult = { this.executionResult = {
...this.executionResult, ...this.executionResult,
chatMessage: chatMessage, chatMessage: chatMessage,
isStreaming: false isStreaming: false,
}; };
} }
} }

View File

@@ -1,55 +1,54 @@
<template> <template>
<div class="output-params" <div class="output-params" v-if="node.method || node.httpParams.url">
v-if="node.method || node.httpParams.url"> <div class="param-item">
<div class="param-item"> <span class="param-name">{{ node.httpParams.method }}</span>
<span class="param-name">{{ node.httpParams.method }}</span> <span class="param-value">{{ node.httpParams.url }}</span>
<span class="param-value">{{ node.httpParams.url }}</span> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import common from './common.ts' import common from './common.ts';
export default { export default {
name: 'HttpNode', name: 'HttpNode',
mixins: [common] mixins: [common],
} };
</script> </script>
<style scoped> <style scoped>
/* 添加样式 */ /* 添加样式 */
.output-params { .output-params {
padding: 8px 12px; padding: 8px 12px;
} }
.param-item { .param-item {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 4px; margin-bottom: 4px;
padding: 3px 10px; padding: 3px 10px;
font-size: 13px; font-size: 13px;
color: #333; color: #333;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
background-color: rgb(242, 244, 247); background-color: rgb(242, 244, 247);
overflow: hidden; overflow: hidden;
} }
.param-icon { .param-icon {
color: rgb(41 112 255); color: rgb(41 112 255);
font-weight: bold; font-weight: bold;
} }
.param-name { .param-name {
padding: 2px 5px; padding: 2px 5px;
background-color: #fff; background-color: #fff;
box-sizing: border-box; box-sizing: border-box;
margin-right: 4px; margin-right: 4px;
font-size: 10px; font-size: 10px;
font-weight: bold; font-weight: bold;
border-radius: 4px; border-radius: 4px;
} }
.param-value { .param-value {
color: #666; color: #666;
font-weight: 500; font-weight: 500;
} }
</style> </style>

View File

@@ -57,4 +57,4 @@ export default {
color: #666; color: #666;
font-weight: 500; font-weight: 500;
} }
</style> </style>

View File

@@ -85,4 +85,4 @@ export default {
line-height: 1.4; line-height: 1.4;
word-break: break-word; word-break: break-word;
} }
</style> </style>

View File

@@ -2,17 +2,17 @@ import { Node } from '../types/node';
import { PropType } from 'vue'; import { PropType } from 'vue';
export default { export default {
inject: ['parent'], inject: ['parent'],
props: { props: {
node: { node: {
type: Object as PropType<Node>, type: Object as PropType<Node>,
required: true required: true,
} },
}, },
data() { data() {
return { return {
inputParams: this.node.inputParams || [], inputParams: this.node.inputParams || [],
outputParams: this.node.outputParams || [] outputParams: this.node.outputParams || [],
} };
} },
} };

View File

@@ -96,7 +96,7 @@ export const nodeTypes: NodeType[] = [
{ {
name: '分支2', name: '分支2',
value: 1, value: 1,
} },
], ],
}, },
outputParams: [ outputParams: [
@@ -151,7 +151,7 @@ export const nodeTypes: NodeType[] = [
{ {
name: 'result', name: 'result',
type: 'String', type: 'String',
} },
], ],
}, },
{ {
@@ -245,7 +245,8 @@ export const nodeTypes: NodeType[] = [
messages: [ messages: [
{ {
role: 'user', role: 'user',
content: '你是一个问题总结助手。\n任务根据用户提问 ${arg1} 和系统答案 ${arg2},生成一个标准的问题答案。\n要求基于系统答案内容回答回答准确、简洁、有用。', content:
'你是一个问题总结助手。\n任务根据用户提问 ${arg1} 和系统答案 ${arg2},生成一个标准的问题答案。\n要求基于系统答案内容回答回答准确、简洁、有用。',
}, },
], ],
modelConfig: { modelConfig: {

View File

@@ -85,11 +85,10 @@
import { Plus, Delete } from '@element-plus/icons-vue'; import { Plus, Delete } from '@element-plus/icons-vue';
import common from './common.ts'; import common from './common.ts';
import './panel.css'; import './panel.css';
import {list} from "/@/api/gen/datasource"; import { list } from '/@/api/gen/datasource';
import CodeEditor from '/@/views/knowledge/aiFlow/components/CodeEditor.vue'; import CodeEditor from '/@/views/knowledge/aiFlow/components/CodeEditor.vue';
import { ref } from 'vue'; import { ref } from 'vue';
export default { export default {
name: 'DbPanel', name: 'DbPanel',
components: { components: {
@@ -100,8 +99,7 @@ export default {
mixins: [common], mixins: [common],
data() { data() {
return { return {
dbList: [ dbList: [],
],
}; };
}, },
created() { created() {
@@ -109,8 +107,8 @@ export default {
}, },
methods: { methods: {
async loadDbList() { async loadDbList() {
const {data} = await list() const { data } = await list();
this.dbList = data this.dbList = data;
}, },
handleDbChange() { handleDbChange() {
if (!this.node.dbParams.dbId) { if (!this.node.dbParams.dbId) {
@@ -126,7 +124,7 @@ export default {
setup() { setup() {
const result = ref(null); const result = ref(null);
return { return {
result result,
}; };
}, },
}; };

View File

@@ -249,4 +249,4 @@ export default {
}, },
}, },
}; };
</script> </script>

View File

@@ -60,12 +60,12 @@
<div class="param-item param-item-margin" v-if="node.mcpParams.mcpId"> <div class="param-item param-item-margin" v-if="node.mcpParams.mcpId">
<div class="flex items-center"> <div class="flex items-center">
<span class="mr-2">请求提示词</span> <span class="mr-2">请求提示词</span>
<el-input <el-input
v-model="node.mcpParams.prompt" v-model="node.mcpParams.prompt"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请求提示词,使用${变量名}引用上方定义的变量" placeholder="请求提示词,使用${变量名}引用上方定义的变量"
class="flex-1" class="flex-1"
/> />
</div> </div>
</div> </div>
@@ -134,7 +134,7 @@ export default {
}, },
computed: { computed: {
selectedMcp() { selectedMcp() {
return this.mcpList.find(mcp => mcp.mcpId === this.node.mcpParams?.mcpId); return this.mcpList.find((mcp) => mcp.mcpId === this.node.mcpParams?.mcpId);
}, },
}, },
async mounted() { async mounted() {
@@ -148,7 +148,7 @@ export default {
} }
await this.fetchMcpList(); await this.fetchMcpList();
// 确保输出参数有默认值 // 确保输出参数有默认值
if (!this.outputParams.length) { if (!this.outputParams.length) {
this.outputParams.push({ this.outputParams.push({
@@ -180,4 +180,4 @@ export default {
.w-full { .w-full {
margin-right: 15px; margin-right: 15px;
} }
</style> </style>

View File

@@ -1,302 +1,267 @@
<template> <template>
<div class="panel-content"> <div class="panel-content">
<!-- 变量列表区域 --> <!-- 变量列表区域 -->
<div class="panel-section"> <div class="panel-section">
<div class="flex justify-between items-center panel-header"> <div class="flex justify-between items-center panel-header">
<span>变量列表</span> <span>变量列表</span>
<el-button type="primary" size="small" @click="addOutput"> <el-button type="primary" size="small" @click="addOutput">
<el-icon> <el-icon>
<Plus/> <Plus />
</el-icon> </el-icon>
添加 添加
</el-button> </el-button>
</div> </div>
<div class="params-list"> <div class="params-list">
<div v-for="(param, index) in inputParams" :key="index" class="mb-2"> <div v-for="(param, index) in inputParams" :key="index" class="mb-2">
<div class="param-item"> <div class="param-item">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div> <div>
<div> <div>
<el-tag type="primary" size="small"> <el-tag type="primary" size="small">
{{ getInputTypeLabel(param.inputType) }} {{ getInputTypeLabel(param.inputType) }}
</el-tag> </el-tag>
<el-text> <el-text> </el-text>
<el-tag :type="param.required ? 'danger' : 'info'" size="small">
</el-text> {{ param.required ? '必填' : '选填' }}
<el-tag :type="param.required ? 'danger' : 'info'" size="small"> </el-tag>
{{ param.required ? '必填' : '选填' }} <el-text> </el-text>
</el-tag> <el-text> {{ param.name }} ({{ param.type }}) </el-text>
<el-text> </div>
</div>
</el-text> <div v-if="!param.disabled" class="flex gap-2">
<el-text> <el-button type="primary" size="small" @click="editParam(index)">
{{ param.name }} ({{ param.type }}) <el-icon>
</el-text> <Edit />
</div> </el-icon>
</div> </el-button>
<div v-if="!param.disabled" class="flex gap-2"> <el-button size="small" @click="removeOutput(index)">
<el-button type="primary" size="small" @click="editParam(index)"> <el-icon>
<el-icon> <Delete />
<Edit/> </el-icon>
</el-icon> </el-button>
</el-button> </div>
<el-button size="small" @click="removeOutput(index)"> </div>
<el-icon> </div>
<Delete/> </div>
</el-icon> </div>
</el-button> </div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 变量编辑对话框 --> <!-- 变量编辑对话框 -->
<el-dialog <el-dialog v-model="editDialogVisible" :title="isEdit ? '编辑变量' : '添加变量'" width="600px" destroy-on-close>
v-model="editDialogVisible" <el-form ref="paramForm" :model="editingParam" :rules="rules" label-position="top">
:title="isEdit ? '编辑变量' : '添加变量'" <el-form-item label="显示名称" prop="name">
width="600px" <el-input v-model="editingParam.name" placeholder="请输入显示名称" />
destroy-on-close </el-form-item>
>
<el-form
ref="paramForm"
:model="editingParam"
:rules="rules"
label-position="top"
>
<el-form-item label="显示名称" prop="name">
<el-input v-model="editingParam.name" placeholder="请输入显示名称"/>
</el-form-item>
<el-form-item label="变量名" prop="type"> <el-form-item label="变量名" prop="type">
<el-input v-model="editingParam.type" placeholder="请输入变量名"/> <el-input v-model="editingParam.type" placeholder="请输入变量名" />
</el-form-item> </el-form-item>
<el-form-item label="输入类型" prop="inputType"> <el-form-item label="输入类型" prop="inputType">
<el-select <el-select v-model="editingParam.inputType" class="w-full" @change="handleEditInputTypeChange">
v-model="editingParam.inputType" <el-option v-for="item in inputTypeDict" :key="item.value" :label="item.label" :value="item.value" />
class="w-full" </el-select>
@change="handleEditInputTypeChange" </el-form-item>
>
<el-option
v-for="item in inputTypeDict"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否必填" prop="required"> <el-form-item label="是否必填" prop="required">
<el-select v-model="editingParam.required" class="w-full"> <el-select v-model="editingParam.required" class="w-full">
<el-option :value="false" label="否"/> <el-option :value="false" label="否" />
<el-option :value="true" label="是"/> <el-option :value="true" label="是" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 选项编辑表单 --> <!-- 选项编辑表单 -->
<template v-if="editingParam.inputType==='select'"> <template v-if="editingParam.inputType === 'select'">
<el-divider content-position="left">选项配置</el-divider> <el-divider content-position="left">选项配置</el-divider>
<div class="options-list"> <div class="options-list">
<div v-for="(option, index) in editingParam.editingOptions" :key="index" class="mb-2"> <div v-for="(option, index) in editingParam.editingOptions" :key="index" class="mb-2">
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="9"> <el-col :span="9">
<el-form-item <el-form-item :prop="'editingOptions.' + index + '.label'" :rules="{ required: true, message: '请输入选项名称', trigger: 'blur' }">
:prop="'editingOptions.' + index + '.label'" <el-input v-model="option.label" placeholder="请输入选项名称" />
:rules="{ required: true, message: '请输入选项名称', trigger: 'blur' }" </el-form-item>
> </el-col>
<el-input v-model="option.label" placeholder="请输入选项名称"/> <el-col :span="12">
</el-form-item> <el-form-item :prop="'editingOptions.' + index + '.value'" :rules="{ required: true, message: '请输入选项值', trigger: 'blur' }">
</el-col> <el-input v-model="option.value" placeholder="请输入选项值" />
<el-col :span="12"> </el-form-item>
<el-form-item </el-col>
:prop="'editingOptions.' + index + '.value'" <el-col :span="3">
:rules="{ required: true, message: '请输入选项值', trigger: 'blur' }" <el-button @click="removeOption(index)">
> <el-icon>
<el-input v-model="option.value" placeholder="请输入选项值"/> <Delete />
</el-form-item> </el-icon>
</el-col> </el-button>
<el-col :span="3"> </el-col>
<el-button @click="removeOption(index)"> </el-row>
<el-icon> </div>
<Delete/> <el-button type="primary" @click="addOption">
</el-icon> <el-icon>
</el-button> <Plus />
</el-col> </el-icon>
</el-row> 添加选项
</div> </el-button>
<el-button type="primary" @click="addOption"> </div>
<el-icon> </template>
<Plus/> </el-form>
</el-icon>
添加选项
</el-button>
</div>
</template>
</el-form>
<template #footer> <template #footer>
<div class="flex gap-2 justify-end"> <div class="flex gap-2 justify-end">
<el-button @click="editDialogVisible = false">取消</el-button> <el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button> <el-button type="primary" @click="handleSave">确定</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
import {Plus, Delete, Edit} from '@element-plus/icons-vue' import { Plus, Delete, Edit } from '@element-plus/icons-vue';
import common from './common.ts' import common from './common.ts';
export default { export default {
name: 'StartPanel', name: 'StartPanel',
components: { components: {
Plus, Plus,
Delete, Delete,
Edit Edit,
}, },
mixins: [common], mixins: [common],
data() { data() {
return { return {
inputTypeDict: [ inputTypeDict: [
{label: '输入框', value: 'input'}, { label: '输入框', value: 'input' },
{label: '下拉框', value: 'select'}, { label: '下拉框', value: 'select' },
{label: '数字框', value: 'number'}, { label: '数字框', value: 'number' },
{label: '文本框', value: 'textarea'}, { label: '文本框', value: 'textarea' },
{label: '图片', value: 'image'}, { label: '图片', value: 'image' },
], ],
editDialogVisible: false, editDialogVisible: false,
editingParam: { editingParam: {
name: '', name: '',
type: '', type: '',
value: '', value: '',
required: false, required: false,
inputType: 'input', inputType: 'input',
options: [], options: [],
editingOptions: [] editingOptions: [],
}, },
isEdit: false, isEdit: false,
rules: { rules: {
name: [ name: [
{required: true, message: '请输入显示名称', trigger: 'blur'}, { required: true, message: '请输入显示名称', trigger: 'blur' },
{min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur'} { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' },
], ],
type: [ type: [
{required: true, message: '请输入变量名', trigger: 'blur'}, { required: true, message: '请输入变量名', trigger: 'blur' },
{ {
pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/,
message: '变量名只能包含字母、数字和下划线,且必须以字母开头', message: '变量名只能包含字母、数字和下划线,且必须以字母开头',
trigger: 'blur' trigger: 'blur',
} },
], ],
inputType: [ inputType: [{ required: true, message: '请选择输入类型', trigger: 'change' }],
{required: true, message: '请选择输入类型', trigger: 'change'} required: [{ required: true, message: '请选择是否必填', trigger: 'change' }],
], },
required: [ };
{required: true, message: '请选择是否必填', trigger: 'change'} },
] mounted() {
} this.updateOutputParams();
} },
}, methods: {
mounted() { addOutput() {
this.updateOutputParams() this.isEdit = false;
}, this.editingParam = {
methods: { name: '',
addOutput() { type: '',
this.isEdit = false value: '',
this.editingParam = { required: false,
name: '', inputType: 'input',
type: '', options: [],
value: '', editingOptions: [],
required: false, };
inputType: 'input', this.editDialogVisible = true;
options: [], },
editingOptions: [] editParam(index) {
} this.isEdit = true;
this.editDialogVisible = true this.editingParamIndex = index;
}, const param = { ...this.inputParams[index] };
editParam(index) { param.editingOptions = [...(param.options || [])].map((opt) => ({ ...opt }));
this.isEdit = true this.editingParam = param;
this.editingParamIndex = index this.editDialogVisible = true;
const param = {...this.inputParams[index]} },
param.editingOptions = [...(param.options || [])].map(opt => ({...opt})) saveParam() {
this.editingParam = param if (this.isEdit) {
this.editDialogVisible = true this.inputParams[this.editingParamIndex] = { ...this.editingParam };
}, } else {
saveParam() { this.inputParams.push({ ...this.editingParam });
if (this.isEdit) { }
this.inputParams[this.editingParamIndex] = {...this.editingParam} this.editDialogVisible = false;
} else { },
this.inputParams.push({...this.editingParam}) handleEditInputTypeChange() {
} if (this.editingParam.inputType === 'select' && (!this.editingParam.options || this.editingParam.options.length === 0)) {
this.editDialogVisible = false this.editingParam.options = [];
}, }
handleEditInputTypeChange() { },
if (this.editingParam.inputType === 'select' && (!this.editingParam.options || this.editingParam.options.length === 0)) { removeOutput(index) {
this.editingParam.options = [] this.inputParams.splice(index, 1);
} },
}, updateOutputParams() {
removeOutput(index) { const outputParams = this.inputParams.map((param) => ({
this.inputParams.splice(index, 1) name: param.type || '',
}, type: param.type || '',
updateOutputParams() { value: '',
const outputParams = this.inputParams.map(param => ({ required: param.required || false,
name: param.type || '', inputType: param.inputType || 'input',
type: param.type || '', options: param.options || [],
value: '', }));
required: param.required || false,
inputType: param.inputType || 'input',
options: param.options || []
}))
if (this.node) { if (this.node) {
this.node.outputParams = outputParams this.node.outputParams = outputParams;
} }
}, },
handleSave() { handleSave() {
this.$refs.paramForm.validate(async (valid) => { this.$refs.paramForm.validate(async (valid) => {
if (valid) { if (valid) {
if (this.editingParam.inputType === 'select') { if (this.editingParam.inputType === 'select') {
this.editingParam.options = [...this.editingParam.editingOptions] this.editingParam.options = [...this.editingParam.editingOptions];
} }
if (this.isEdit) { if (this.isEdit) {
this.inputParams[this.editingParamIndex] = {...this.editingParam} this.inputParams[this.editingParamIndex] = { ...this.editingParam };
} else { } else {
this.inputParams.push({...this.editingParam}) this.inputParams.push({ ...this.editingParam });
} }
this.editDialogVisible = false this.editDialogVisible = false;
} else { } else {
return false return false;
} }
}) });
}, },
addOption() { addOption() {
this.editingParam.editingOptions.push({ this.editingParam.editingOptions.push({
label: '', label: '',
value: '' value: '',
}) });
}, },
removeOption(index) { removeOption(index) {
this.editingParam.editingOptions.splice(index, 1) this.editingParam.editingOptions.splice(index, 1);
}, },
getInputTypeLabel(type) { getInputTypeLabel(type) {
const found = this.inputTypeDict.find(item => item.value === type) const found = this.inputTypeDict.find((item) => item.value === type);
return found ? found.label : '输入框' return found ? found.label : '输入框';
} },
}, },
watch: { watch: {
inputParams: { inputParams: {
handler() { handler() {
this.updateOutputParams() this.updateOutputParams();
}, },
deep: true deep: true,
} },
} },
} };
</script> </script>
<style> <style>

View File

@@ -55,12 +55,7 @@
/> />
</div> </div>
<div class="text-tips"> <div class="text-tips">
<el-alert <el-alert title="提示:此节点将返回您设置的固定文本内容,可用于流程中的固定消息、说明文字等场景。" type="info" :closable="false" show-icon />
title="提示:此节点将返回您设置的固定文本内容,可用于流程中的固定消息、说明文字等场景。"
type="info"
:closable="false"
show-icon
/>
</div> </div>
</div> </div>
@@ -136,4 +131,4 @@ export default {
.param-item.readonly .el-input { .param-item.readonly .el-input {
background-color: #f5f7fa; background-color: #f5f7fa;
} }
</style> </style>

View File

@@ -8,140 +8,140 @@ import { InputParams, Params } from './utils';
* 节点执行结果接口 * 节点执行结果接口
*/ */
export interface NodeExecutionResponse { export interface NodeExecutionResponse {
nodeId: string; nodeId: string;
type: string; type: string;
result: any; result: any;
timestamp: number; timestamp: number;
tokens?: number; tokens?: number;
} }
/** /**
* 代码节点参数接口 * 代码节点参数接口
*/ */
export interface CodeNodeParams { export interface CodeNodeParams {
code: string; code: string;
} }
/** /**
* 数据库节点参数接口 * 数据库节点参数接口
*/ */
export interface DbNodeParams { export interface DbNodeParams {
dbId: string | number; dbId: string | number;
sql: string; sql: string;
} }
/** /**
* HTTP节点参数接口 * HTTP节点参数接口
*/ */
export interface HttpNodeParams { export interface HttpNodeParams {
url: string; url: string;
method: string; method: string;
contentType: string; contentType: string;
jsonBody?: string; jsonBody?: string;
headerParams?: Array<{ headerParams?: Array<{
name: string; name: string;
type: string; type: string;
}>; }>;
bodyParams?: Array<{ bodyParams?: Array<{
name: string; name: string;
type: string; type: string;
}>; }>;
paramsParams?: Array<{ paramsParams?: Array<{
name: string; name: string;
type: string; type: string;
}>; }>;
} }
/** /**
* LLM节点参数接口 * LLM节点参数接口
*/ */
export interface LLMNodeParams { export interface LLMNodeParams {
modelConfig: Record<string, any>; modelConfig: Record<string, any>;
messages: Array<{ messages: Array<{
role: string; role: string;
content: string; content: string;
}>; }>;
} }
/** /**
* 通知节点参数接口 * 通知节点参数接口
*/ */
export interface NoticeNodeParams { export interface NoticeNodeParams {
templateCode: string; templateCode: string;
} }
/** /**
* 问题节点参数接口 * 问题节点参数接口
*/ */
export interface QuestionNodeParams { export interface QuestionNodeParams {
question: string; question: string;
} }
/** /**
* Switch节点参数接口 * Switch节点参数接口
*/ */
export interface SwitchNodeParams { export interface SwitchNodeParams {
cases: Array<{ cases: Array<{
value: any; value: any;
}>; }>;
} }
/** /**
* 代码节点接口 * 代码节点接口
*/ */
export interface CodeNode extends Node { export interface CodeNode extends Node {
codeParams: CodeNodeParams; codeParams: CodeNodeParams;
} }
/** /**
* 数据库节点接口 * 数据库节点接口
*/ */
export interface DbNode extends Node { export interface DbNode extends Node {
dbParams: DbNodeParams; dbParams: DbNodeParams;
} }
/** /**
* HTTP节点接口 * HTTP节点接口
*/ */
export interface HttpNode extends Node { export interface HttpNode extends Node {
httpParams: HttpNodeParams; httpParams: HttpNodeParams;
} }
/** /**
* LLM节点接口 * LLM节点接口
*/ */
export interface LLMNode extends Node { export interface LLMNode extends Node {
llmParams: LLMNodeParams; llmParams: LLMNodeParams;
} }
/** /**
* 通知节点接口 * 通知节点接口
*/ */
export interface NoticeNode extends Node { export interface NoticeNode extends Node {
noticeParams: NoticeNodeParams; noticeParams: NoticeNodeParams;
} }
/** /**
* 问题节点接口 * 问题节点接口
*/ */
export interface QuestionNode extends Node { export interface QuestionNode extends Node {
questionParams: QuestionNodeParams; questionParams: QuestionNodeParams;
} }
/** /**
* Switch节点接口 * Switch节点接口
*/ */
export interface SwitchNode extends Node { export interface SwitchNode extends Node {
switchParams: SwitchNodeParams; switchParams: SwitchNodeParams;
code?: string; code?: string;
} }
/** /**
* 执行器函数类型 * 执行器函数类型
*/ */
export type ExecutorFunction<T extends Node> = ( export type ExecutorFunction<T extends Node> = (
node: T, node: T,
inputParams: InputParams, inputParams: InputParams,
nodes: Node[], nodes: Node[],
options?: ExecutionOptions options?: ExecutionOptions
) => Promise<NodeExecutionResponse>; ) => Promise<NodeExecutionResponse>;

View File

@@ -4,24 +4,24 @@ import { Node, Connection, ParamItem } from './node';
// Panel component interface // Panel component interface
export interface PanelComponent { export interface PanelComponent {
parent: { parent: {
nodes: Node[]; nodes: Node[];
connections: Connection[]; connections: Connection[];
env?: Array<{ name: string; value: any }>; env?: Array<{ name: string; value: any }>;
}; };
node: Node; node: Node;
$emit: (event: string, ...args: any[]) => void; $emit: (event: string, ...args: any[]) => void;
} }
// Node with params // Node with params
export interface NodeWithParams extends Node { export interface NodeWithParams extends Node {
inputParams: ParamItem[]; inputParams: ParamItem[];
outputParams: ParamItem[]; outputParams: ParamItem[];
} }
// Previous node output structure // Previous node output structure
export interface PreviousNodeOutput { export interface PreviousNodeOutput {
id: string; id: string;
name: string; name: string;
list: ParamItem[]; list: ParamItem[];
} }

View File

@@ -1,22 +1,22 @@
import { FlowClass } from '../types/utils'; import { FlowClass } from '../types/utils';
export default class Flow implements FlowClass { export default class Flow implements FlowClass {
/** /**
* 获取统一的URL参数 * 获取统一的URL参数
* @param type - 流程类型 * @param type - 流程类型
* @param id - 流程ID * @param id - 流程ID
* @returns 格式化的URL字符串 * @returns 格式化的URL字符串
*/ */
static runUrl(type: string, id: string): string { static runUrl(type: string, id: string): string {
return `/flow/${type}/${id}`; return `/flow/${type}/${id}`;
} }
/** /**
* 获取icon地址 * 获取icon地址
* @param icon - 图标路径 * @param icon - 图标路径
* @returns 图标路径或默认图标 * @returns 图标路径或默认图标
*/ */
static getIcon(icon?: string): string { static getIcon(icon?: string): string {
return icon ? icon : '/img/chat/icon.png'; return icon ? icon : '/img/chat/icon.png';
} }
} }

View File

@@ -132,7 +132,7 @@
> >
<el-icon><ChatDotRound /></el-icon> <el-icon><ChatDotRound /></el-icon>
</el-button> </el-button>
<div class="flex-grow ml-4"></div> <div class="flex-grow ml-4"></div>
<div class="flex items-center text-xs text-gray-500 dark:text-gray-400"> <div class="flex items-center text-xs text-gray-500 dark:text-gray-400">
<el-icon class="mr-1"><Clock /></el-icon> <el-icon class="mr-1"><Clock /></el-icon>
@@ -158,7 +158,7 @@
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" /> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 工具抽屉 --> <!-- 工具抽屉 -->
<tools-drawer ref="toolsDrawerRef" /> <tools-drawer ref="toolsDrawerRef" />
</div> </div>

View File

@@ -1,50 +1,50 @@
<template> <template>
<el-drawer v-model="visible" title="MCP 工具能力" size="40%" direction="rtl" :before-close="handleClose"> <el-drawer v-model="visible" title="MCP 工具能力" size="40%" direction="rtl" :before-close="handleClose">
<!-- 工具列表 --> <!-- 工具列表 -->
<div v-loading="loading"> <div v-loading="loading">
<div v-if="toolsList.length === 0 && !loading" class="text-center py-12"> <div v-if="toolsList.length === 0 && !loading" class="text-center py-12">
<el-empty description="暂无工具数据" /> <el-empty description="暂无工具数据" />
</div> </div>
<div v-else class="space-y-4"> <div v-else class="space-y-4">
<div <div
v-for="(tool, index) in toolsList" v-for="(tool, index) in toolsList"
:key="index" :key="index"
class="p-4 border border-gray-200 rounded-lg transition-all duration-200 hover:shadow-md dark:border-gray-700 dark:bg-gray-800" class="p-4 border border-gray-200 rounded-lg transition-all duration-200 hover:shadow-md dark:border-gray-700 dark:bg-gray-800"
> >
<div class="flex items-start justify-between mb-3"> <div class="flex items-start justify-between mb-3">
<div class="flex-1"> <div class="flex-1">
<h4 class="text-base font-medium text-gray-900 dark:text-white"> <h4 class="text-base font-medium text-gray-900 dark:text-white">
{{ tool.name || '未命名工具' }} {{ tool.name || '未命名工具' }}
</h4> </h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ tool.description || '暂无描述' }} {{ tool.description || '暂无描述' }}
</p> </p>
</div>
<el-tag size="small" type="primary">工具</el-tag>
</div>
<!-- 工具详细信息 -->
<div class="space-y-3">
<!-- 输入参数 -->
<div v-if="tool.inputSchema">
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输入参数:</h5>
<div class="p-3 bg-gray-50 rounded dark:bg-gray-700">
<pre class="text-xs text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{{ formatSchema(tool.inputSchema) }}</pre>
</div> </div>
<el-tag size="small" type="primary">工具</el-tag>
</div> </div>
<!-- 工具详细信息 --> <!-- 输出参数 -->
<div class="space-y-3"> <div v-if="tool.outputSchema">
<!-- 参数 --> <h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">参数:</h5>
<div v-if="tool.inputSchema"> <div class="p-3 bg-gray-50 rounded dark:bg-gray-700">
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输入参数:</h5> <pre class="text-xs text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{{ formatSchema(tool.outputSchema) }}</pre>
<div class="p-3 bg-gray-50 rounded dark:bg-gray-700">
<pre class="text-xs text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{{ formatSchema(tool.inputSchema) }}</pre>
</div>
</div>
<!-- 输出参数 -->
<div v-if="tool.outputSchema">
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">输出参数:</h5>
<div class="p-3 bg-gray-50 rounded dark:bg-gray-700">
<pre class="text-xs text-gray-800 dark:text-gray-200 whitespace-pre-wrap">{{ formatSchema(tool.outputSchema) }}</pre>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
@@ -107,7 +107,7 @@ const formatSchema = (schema: any) => {
const handleClose = () => { const handleClose = () => {
visible.value = false; visible.value = false;
toolsList.value = []; toolsList.value = [];
Object.keys(mcpInfo).forEach(key => delete mcpInfo[key]); Object.keys(mcpInfo).forEach((key) => delete mcpInfo[key]);
}; };
// 暴露方法 // 暴露方法

View File

@@ -11,7 +11,9 @@
rows="2" rows="2"
/> />
<div class="absolute -bottom-5 right-2 text-xs z-10 bg-white/90 px-2 py-1 rounded"> <div class="absolute -bottom-5 right-2 text-xs z-10 bg-white/90 px-2 py-1 rounded">
<a href="https://cloud.siliconflow.cn/i/YKcJJTYP" target="_blank" class="text-blue-500 hover:text-blue-700 no-underline text-xs">获取硅基流动 免费 ApiKey</a> <a href="https://cloud.siliconflow.cn/i/YKcJJTYP" target="_blank" class="text-blue-500 hover:text-blue-700 no-underline text-xs"
>获取硅基流动 免费 ApiKey</a
>
</div> </div>
</el-form-item> </el-form-item>
@@ -28,7 +30,7 @@
</el-card> </el-card>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="visible = false">取消</el-button> <el-button @click="visible = false">取消</el-button>
@@ -52,14 +54,14 @@ const loading = ref(false);
// 提交表单数据 // 提交表单数据
const form = reactive({ const form = reactive({
apiKey: '' apiKey: '',
}); });
// 定义校验规则 // 定义校验规则
const dataRules = ref({ const dataRules = ref({
apiKey: [ apiKey: [
{ required: true, message: '请输入API Key', trigger: 'blur' }, { required: true, message: '请输入API Key', trigger: 'blur' },
{ {
validator: (rule: any, value: any, callback: any) => { validator: (rule: any, value: any, callback: any) => {
if (!value) { if (!value) {
callback(new Error('请输入API Key')); callback(new Error('请输入API Key'));
@@ -74,86 +76,86 @@ const dataRules = ref({
return; return;
} }
callback(); callback();
}, },
trigger: 'blur' trigger: 'blur',
} },
] ],
}); });
// 预览要创建的模型 // 预览要创建的模型
const previewModels = ref([ const previewModels = ref([
{ {
type: 'Chat', type: 'Chat',
typeLabel: '聊天', typeLabel: '聊天',
modelName: 'moonshotai/Kimi-K2-Instruct', modelName: 'moonshotai/Kimi-K2-Instruct',
name: 'kimi-k2', name: 'kimi-k2',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Chat', type: 'Chat',
typeLabel: '聊天', typeLabel: '聊天',
modelName: 'deepseek-ai/DeepSeek-V3', modelName: 'deepseek-ai/DeepSeek-V3',
name: 'deepseek-chat', name: 'deepseek-chat',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Embedding', type: 'Embedding',
typeLabel: '向量', typeLabel: '向量',
modelName: 'Qwen/Qwen3-Embedding-8B', modelName: 'Qwen/Qwen3-Embedding-8B',
name: 'qwen-embedding', name: 'qwen-embedding',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Reranker', type: 'Reranker',
typeLabel: '排序', typeLabel: '排序',
modelName: 'Qwen/Qwen3-Reranker-8B', modelName: 'Qwen/Qwen3-Reranker-8B',
name: 'qwen-reranker', name: 'qwen-reranker',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Image', type: 'Image',
typeLabel: '图片', typeLabel: '图片',
modelName: 'Kwai-Kolors/Kolors', modelName: 'Kwai-Kolors/Kolors',
name: 'kolors-image', name: 'kolors-image',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Reason', type: 'Reason',
typeLabel: '推理', typeLabel: '推理',
modelName: 'MiniMaxAI/MiniMax-M1-80k', modelName: 'MiniMaxAI/MiniMax-M1-80k',
name: 'minimax-reason', name: 'minimax-reason',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Vision', type: 'Vision',
typeLabel: '视觉', typeLabel: '视觉',
modelName: 'Qwen/Qwen2.5-VL-72B-Instruct', modelName: 'Qwen/Qwen2.5-VL-72B-Instruct',
name: 'qwen-vision', name: 'qwen-vision',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Video', type: 'Video',
typeLabel: '视频', typeLabel: '视频',
modelName: 'Wan-AI/Wan2.1-I2V-14B-720P', modelName: 'Wan-AI/Wan2.1-I2V-14B-720P',
name: 'wan-video', name: 'wan-video',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
}, },
{ {
type: 'Voice', type: 'Voice',
typeLabel: '音频', typeLabel: '音频',
modelName: 'RVC-Boss/GPT-SoVITS', modelName: 'RVC-Boss/GPT-SoVITS',
name: 'gpt-sovits', name: 'gpt-sovits',
provider: 'Siliconflow', provider: 'Siliconflow',
baseUrl: 'https://api.siliconflow.cn/v1' baseUrl: 'https://api.siliconflow.cn/v1',
} },
]); ]);
// 检查模型名称是否存在 // 检查模型名称是否存在
@@ -169,7 +171,7 @@ const checkModelExists = async (name: string): Promise<boolean> => {
// 打开弹窗 // 打开弹窗
const openDialog = () => { const openDialog = () => {
visible.value = true; visible.value = true;
// 重置表单数据 // 重置表单数据
nextTick(() => { nextTick(() => {
dataFormRef.value?.resetFields(); dataFormRef.value?.resetFields();
@@ -184,7 +186,7 @@ const onSubmit = async () => {
try { try {
loading.value = true; loading.value = true;
let successCount = 0; let successCount = 0;
let failedCount = 0; let failedCount = 0;
let skipCount = 0; let skipCount = 0;
@@ -215,14 +217,14 @@ const onSubmit = async () => {
...(model.type === 'Chat' && { ...(model.type === 'Chat' && {
responseLimit: 2048, responseLimit: 2048,
temperature: 0.4, temperature: 0.4,
topP: 0.7 topP: 0.7,
}), }),
// 为Image类型设置默认参数 // 为Image类型设置默认参数
...(model.type === 'Image' && { ...(model.type === 'Image' && {
imageSize: '1024x1024', imageSize: '1024x1024',
imageQuality: 'standard', imageQuality: 'standard',
imageStyle: 'natural' imageStyle: 'natural',
}) }),
}; };
await addObj(modelData); await addObj(modelData);
@@ -248,17 +250,17 @@ const onSubmit = async () => {
if (resultMessages.length > 0) { if (resultMessages.length > 0) {
useMessage().success(resultMessages.join('')); useMessage().success(resultMessages.join(''));
} }
// 显示跳过的模型信息 // 显示跳过的模型信息
if (skipped.length > 0 && skipped.length <= 3) { if (skipped.length > 0 && skipped.length <= 3) {
skipped.forEach(msg => useMessage().warning(msg)); skipped.forEach((msg) => useMessage().warning(msg));
} else if (skipped.length > 3) { } else if (skipped.length > 3) {
useMessage().warning(`共跳过${skipCount}个已存在的模型`); useMessage().warning(`共跳过${skipCount}个已存在的模型`);
} }
// 显示错误信息 // 显示错误信息
if (errors.length > 0 && errors.length <= 3) { if (errors.length > 0 && errors.length <= 3) {
errors.forEach(error => useMessage().error(error)); errors.forEach((error) => useMessage().error(error));
} else if (errors.length > 3) { } else if (errors.length > 3) {
useMessage().error(`批量创建完成,有${failedCount}个模型创建失败`); useMessage().error(`批量创建完成,有${failedCount}个模型创建失败`);
} }
@@ -278,4 +280,4 @@ const onSubmit = async () => {
defineExpose({ defineExpose({
openDialog, openDialog,
}); });
</script> </script>

View File

@@ -133,7 +133,7 @@
</el-col> </el-col>
</template> </template>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="24" class="mb20"> <el-col :span="24" class="mb20">
<el-form-item prop="extData"> <el-form-item prop="extData">
@@ -204,11 +204,9 @@ const availableDimensions = computed(() => {
if (form.modelType !== 'Embedding' || !form.modelName) { if (form.modelType !== 'Embedding' || !form.modelName) {
return []; return [];
} }
const currentModel = availableModels.value.find(model => const currentModel = availableModels.value.find((model) => model.model === form.modelName && model.type === 'Embedding');
model.model === form.modelName && model.type === 'Embedding'
);
return currentModel?.dimensions || []; return currentModel?.dimensions || [];
}); });
@@ -259,7 +257,7 @@ const dataRules = ref({
], ],
imageSize: [{ required: true, message: '请选择图片大小', trigger: 'change' }], imageSize: [{ required: true, message: '请选择图片大小', trigger: 'change' }],
dimensions: [ dimensions: [
{ {
validator: (rule: any, value: any, callback: any) => { validator: (rule: any, value: any, callback: any) => {
// 只有当显示维度下拉框时才验证必填 // 只有当显示维度下拉框时才验证必填
if (form.modelType === 'Embedding' && availableDimensions.value.length > 0) { if (form.modelType === 'Embedding' && availableDimensions.value.length > 0) {
@@ -269,9 +267,9 @@ const dataRules = ref({
} }
} }
callback(); callback();
}, },
trigger: 'change' trigger: 'change',
} },
], ],
extData: [{ validator: rule.json, trigger: 'blur' }], extData: [{ validator: rule.json, trigger: 'blur' }],
// ... 其他字段的验证规则 ... // ... 其他字段的验证规则 ...
@@ -374,9 +372,7 @@ watch(
() => form.modelName, () => form.modelName,
() => { () => {
if (form.modelType === 'Embedding') { if (form.modelType === 'Embedding') {
const currentModel = availableModels.value.find(model => const currentModel = availableModels.value.find((model) => model.model === form.modelName && model.type === 'Embedding');
model.model === form.modelName && model.type === 'Embedding'
);
if (currentModel?.dimensions && currentModel.dimensions.length > 0) { if (currentModel?.dimensions && currentModel.dimensions.length > 0) {
form.dimensions = currentModel.dimensions[0].toString(); form.dimensions = currentModel.dimensions[0].toString();
} else { } else {
@@ -387,6 +383,4 @@ watch(
} }
} }
); );
</script> </script>

View File

@@ -88,7 +88,7 @@
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" /> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 批量新增 --> <!-- 批量新增 -->
<batch-form-dialog ref="batchFormDialogRef" @refresh="getDataList(false)" /> <batch-form-dialog ref="batchFormDialogRef" @refresh="getDataList(false)" />
</div> </div>

View File

@@ -61,7 +61,7 @@ const dataRules = {
prompt: [{ required: true, message: '请输入提示词', trigger: 'blur' }], prompt: [{ required: true, message: '请输入提示词', trigger: 'blur' }],
promptSort: [ promptSort: [
{ required: true, message: '请输入列表排序', trigger: 'blur' }, { required: true, message: '请输入列表排序', trigger: 'blur' },
{ type: 'number', message: '列表排序必须为数字', trigger: 'blur' } { type: 'number', message: '列表排序必须为数字', trigger: 'blur' },
], ],
promptStatus: [{ required: true, message: '请选择是否有效', trigger: 'change' }], promptStatus: [{ required: true, message: '请选择是否有效', trigger: 'change' }],
}; };
@@ -109,7 +109,7 @@ function getAiPromptData(id: string) {
const data = res.data; const data = res.data;
Object.assign(form, { Object.assign(form, {
...data, ...data,
promptSort: Number(data.promptSort) promptSort: Number(data.promptSort),
}); });
}) })
.finally(() => { .finally(() => {

View File

@@ -69,7 +69,7 @@ import 'splitpanes/dist/splitpanes.css';
import ControlPanel from './components/ControlPanel.vue'; import ControlPanel from './components/ControlPanel.vue';
import DataDisplay from './components/DataDisplay.vue'; import DataDisplay from './components/DataDisplay.vue';
import { putObj, getObj, parseDoc } from '/@/api/knowledge/aiReportConf'; import { putObj, getObj, parseDoc } from '/@/api/knowledge/aiReportConf';
// @ts-ignore // @ts-ignore
import VueOfficeDocx from '@vue-office/docx/lib/v3/vue-office-docx.mjs'; import VueOfficeDocx from '@vue-office/docx/lib/v3/vue-office-docx.mjs';
import '@vue-office/docx/lib/v3/index.css'; import '@vue-office/docx/lib/v3/index.css';
import other from '/@/utils/other'; import other from '/@/utils/other';

View File

@@ -1,139 +1,125 @@
<template> <template>
<el-drawer <el-drawer v-model="visible" :close-on-click-modal="true" size="50%" :destroy-on-close="true" direction="rtl" class="document-drawer">
v-model="visible" <template #header>
:close-on-click-modal="true" <div class="flex gap-3 items-center py-1">
size="50%" <h2 class="max-w-md text-lg font-semibold text-gray-800 truncate">{{ documentTitle }}</h2>
:destroy-on-close="true" </div>
direction="rtl" </template>
class="document-drawer"
> <div class="px-4 py-2 h-full">
<template #header> <!-- 选项卡 -->
<div class="flex gap-3 items-center py-1"> <el-tabs v-model="activeTab" class="h-full flex flex-col">
<h2 class="max-w-md text-lg font-semibold text-gray-800 truncate">{{ documentTitle }}</h2> <!-- 文档展示选项卡 -->
</div> <el-tab-pane label="文档展示" name="document" class="flex-1">
</template> <div class="h-full">
<el-scrollbar height="calc(100vh - 180px)" ref="scrollbarRef" @scroll="onScroll">
<div class="px-4 py-2 h-full"> <div class="document-content">
<!-- 选项卡 --> <div
<el-tabs v-model="activeTab" class="h-full flex flex-col"> v-for="(slice, index) in slices"
<!-- 文档展示选项卡 --> :key="slice.id"
<el-tab-pane label="文档展示" name="document" class="flex-1"> class="slice-section relative"
<div class="h-full"> :class="{ 'slice-highlight': selectedSliceId === slice.id }"
<el-scrollbar height="calc(100vh - 180px)" ref="scrollbarRef" @scroll="onScroll"> @click="selectSlice(slice)"
<div class="document-content"> @dblclick="startEditing(slice)"
<div :id="`slice-${slice.id}`"
v-for="(slice, index) in slices" >
:key="slice.id" <!-- Slice info - only shown when selected, positioned at top-right -->
class="slice-section relative" <div
:class="{ 'slice-highlight': selectedSliceId === slice.id }" v-if="selectedSliceId === slice.id"
@click="selectSlice(slice)" class="slice-info absolute top-2 right-2 z-10 bg-white dark:bg-gray-800 rounded-md shadow-sm px-4 py-3 border border-gray-200"
@dblclick="startEditing(slice)" >
:id="`slice-${slice.id}`" <div class="flex items-center gap-4 text-sm">
> <span v-if="selectedSlice?.sliceStatus === '1'" class="px-2 py-1 bg-primary text-white text-xs font-bold rounded">
<!-- Slice info - only shown when selected, positioned at top-right --> 已训练
<div </span>
v-if="selectedSliceId === slice.id" <div class="flex items-center">
class="slice-info absolute top-2 right-2 z-10 bg-white dark:bg-gray-800 rounded-md shadow-sm px-4 py-3 border border-gray-200" <svg class="w-4 h-4 mr-1 text-gray-500" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
> <path
<div class="flex items-center gap-4 text-sm"> d="M639.2 282.6l80.2-80.6c5.9-5.6 15.3-5.6 20.7 0 2.3 2.3 3.3 4.3 4 6.6V209.2l15 55.7 55.7 15 0.3 0.2h0.2c2.3 0.7 4.7 1.6 6.4 3.8 5.7 5.6 5.7 14.8 0 20.7l-80 80.2c-3.8 3.7-9 5.2-14.4 3.8l-45.1-12-23 23.3c25.1 32 39.8 72.9 39.8 117.2 0 52.5-21.4 100.7-56.2 135.5l-0.7 0.5c-35 34.8-82.4 56-135 56-52.9 0-101.1-21.6-135.7-56.5-34.8-34.8-56.4-83-56.4-135.5 0-53.1 21.6-100.9 56.4-135.9C406 346.4 454.2 325 507.1 325c44.2 0 84.9 14.8 117.3 39.8l23.1-22.8-12.2-45.4c-1.5-4.9 0.2-10.5 3.9-14z"
<span v-if="selectedSlice?.sliceStatus === '1'" class="px-2 py-1 bg-primary text-white text-xs font-bold rounded"> fill="currentColor"
已训练 ></path>
</span> </svg>
<div class="flex items-center"> <span>命中: {{ selectedSlice?.hitCount || 0 }}</span>
<svg </div>
class="w-4 h-4 mr-1 text-gray-500" <div class="flex items-center">
viewBox="0 0 1024 1024" <svg class="mr-1 w-5 h-5" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
version="1.1" <path
xmlns="http://www.w3.org/2000/svg" d="M109.568 99.328s-17.408 15.36 0 38.912 295.936 392.192 295.936 392.192 17.408 15.36 0 35.84-288.768 323.584-288.768 323.584-20.48 32.768 3.072 32.768h733.184s27.648 0 33.792-29.696c7.168-29.696 33.792-154.624 33.792-154.624s0-12.288-10.24-17.408c-10.24-6.144-30.72 0-33.792 9.216-3.072 9.216-92.16 98.304-125.952 104.448h-471.04l237.568-273.408s17.408-12.288 17.408-29.696-23.552-45.056-23.552-45.056L276.48 169.984h458.752s88.064 47.104 146.432 136.192c7.168 9.216 40.96 9.216 40.96 0s-51.2-201.728-51.2-201.728l-761.856-5.12z"
> fill="currentColor"
<path ></path>
d="M639.2 282.6l80.2-80.6c5.9-5.6 15.3-5.6 20.7 0 2.3 2.3 3.3 4.3 4 6.6V209.2l15 55.7 55.7 15 0.3 0.2h0.2c2.3 0.7 4.7 1.6 6.4 3.8 5.7 5.6 5.7 14.8 0 20.7l-80 80.2c-3.8 3.7-9 5.2-14.4 3.8l-45.1-12-23 23.3c25.1 32 39.8 72.9 39.8 117.2 0 52.5-21.4 100.7-56.2 135.5l-0.7 0.5c-35 34.8-82.4 56-135 56-52.9 0-101.1-21.6-135.7-56.5-34.8-34.8-56.4-83-56.4-135.5 0-53.1 21.6-100.9 56.4-135.9C406 346.4 454.2 325 507.1 325c44.2 0 84.9 14.8 117.3 39.8l23.1-22.8-12.2-45.4c-1.5-4.9 0.2-10.5 3.9-14z" </svg>
fill="currentColor" <span>字符: {{ selectedSlice?.charCount || 0 }}</span>
></path> </div>
</svg> </div>
<span>命中: {{ selectedSlice?.hitCount || 0 }}</span>
</div> <div class="flex mt-3 gap-3">
<div class="flex items-center"> <el-button size="small" class="flex items-center" type="primary" @click="startEditing(slice)" text>
<svg <el-icon class="mr-1"><Edit /></el-icon>编辑
class="mr-1 w-5 h-5" </el-button>
viewBox="0 0 1024 1024" <el-button
version="1.1" size="small"
xmlns="http://www.w3.org/2000/svg" class="flex items-center"
> type="warning"
<path @click="handleRetrain(slice)"
d="M109.568 99.328s-17.408 15.36 0 38.912 295.936 392.192 295.936 392.192 17.408 15.36 0 35.84-288.768 323.584-288.768 323.584-20.48 32.768 3.072 32.768h733.184s27.648 0 33.792-29.696c7.168-29.696 33.792-154.624 33.792-154.624s0-12.288-10.24-17.408c-10.24-6.144-30.72 0-33.792 9.216-3.072 9.216-92.16 98.304-125.952 104.448h-471.04l237.568-273.408s17.408-12.288 17.408-29.696-23.552-45.056-23.552-45.056L276.48 169.984h458.752s88.064 47.104 146.432 136.192c7.168 9.216 40.96 9.216 40.96 0s-51.2-201.728-51.2-201.728l-761.856-5.12z" text
fill="currentColor" v-if="slice.sliceStatus === '1'"
></path> >
</svg> <el-icon class="mr-1"><Refresh /></el-icon>重新训练
<span>字符: {{ selectedSlice?.charCount || 0 }}</span> </el-button>
</div> </div>
</div> </div>
<div class="flex mt-3 gap-3"> <!-- 显示内容或编辑框 -->
<el-button size="small" class="flex items-center" type="primary" @click="startEditing(slice)" text> <div v-if="!editingSlice || editingSlice.id !== slice.id">
<el-icon class="mr-1"><Edit /></el-icon>编辑 <MdRenderer :source="slice.content" :key="`md-${slice.id}`" />
</el-button> </div>
<el-button size="small" class="flex items-center" type="warning" @click="handleRetrain(slice)" text v-if="slice.sliceStatus === '1'"> <el-input
<el-icon class="mr-1"><Refresh /></el-icon>重新训练 v-else
</el-button> type="textarea"
</div> v-model="editingContent"
</div> :autosize="{ minRows: 6, maxRows: 10 }"
@blur="saveContent(slice)"
<!-- 显示内容或编辑框 --> ref="editInputRef"
<div v-if="!editingSlice || editingSlice.id !== slice.id"> class="slice-editor"
<MdRenderer :source="slice.content" :key="`md-${slice.id}`" /> />
</div> </div>
<el-input
v-else <div v-if="slices.length === 0" class="text-center py-10 text-gray-500">当前文档暂无切片内容</div>
type="textarea"
v-model="editingContent" <div v-if="loading" class="text-center py-4">
:autosize="{ minRows: 6, maxRows: 10 }" <el-icon class="is-loading"><Loading /></el-icon>
@blur="saveContent(slice)" <span class="ml-2">加载中...</span>
ref="editInputRef" </div>
class="slice-editor"
/> <div v-if="noMoreData && slices.length > 0" class="text-center py-4 text-gray-400 text-sm">没有更多数据了</div>
</div>
<!-- Add a loading trigger element at the bottom -->
<div v-if="slices.length === 0" class="text-center py-10 text-gray-500"> <div v-if="!noMoreData && !loading" class="loading-trigger h-10" ref="loadTriggerRef"></div>
当前文档暂无切片内容 </div>
</div> </el-scrollbar>
</div>
<div v-if="loading" class="text-center py-4"> </el-tab-pane>
<el-icon class="is-loading"><Loading /></el-icon>
<span class="ml-2">加载中...</span> <!-- 文档关键词选项卡 -->
</div> <el-tab-pane label="文档关键词" name="keywords" class="flex-1">
<div class="h-full flex items-center justify-center">
<div v-if="noMoreData && slices.length > 0" class="text-center py-4 text-gray-400 text-sm"> <div v-if="!props.documentId" class="text-center text-gray-500">
没有更多数据了 <el-icon class="text-2xl mb-2"><Picture /></el-icon>
</div> <div>请选择文档</div>
</div>
<!-- Add a loading trigger element at the bottom --> <div v-else class="w-full h-full flex items-center justify-center">
<div v-if="!noMoreData && !loading" class="loading-trigger h-10" ref="loadTriggerRef"></div> <img
</div> :src="`${baseURL}${other.adaptationUrl('/knowledge/aiDocument/wordcloud')}?documentId=${props.documentId}`"
</el-scrollbar> alt="关键词云图"
</div> class="max-w-full max-h-full object-contain"
</el-tab-pane> style="max-height: calc(100vh - 200px)"
/>
<!-- 文档关键词选项卡 --> </div>
<el-tab-pane label="文档关键词" name="keywords" class="flex-1"> </div>
<div class="h-full flex items-center justify-center"> </el-tab-pane>
<div v-if="!props.documentId" class="text-center text-gray-500"> </el-tabs>
<el-icon class="text-2xl mb-2"><Picture /></el-icon> </div>
<div>请选择文档</div> </el-drawer>
</div>
<div v-else class="w-full h-full flex items-center justify-center">
<img
:src="`${baseURL}${other.adaptationUrl('/knowledge/aiDocument/wordcloud')}?documentId=${props.documentId}`"
alt="关键词云图"
class="max-w-full max-h-full object-contain"
style="max-height: calc(100vh - 200px);"
/>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -147,25 +133,25 @@ import { useIntersectionObserver } from '@vueuse/core';
import other from '/@/utils/other'; import other from '/@/utils/other';
const props = defineProps({ const props = defineProps({
documentId: { documentId: {
type: String, type: String,
default: '' default: '',
}, },
sliceId: { sliceId: {
type: String, type: String,
default: '' default: '',
}, },
modelValue: { modelValue: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
const visible = computed({ const visible = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => emit('update:modelValue', val) set: (val) => emit('update:modelValue', val),
}); });
const documentTitle = ref('文档查看'); const documentTitle = ref('文档查看');
@@ -191,272 +177,283 @@ const imageError = ref('');
// 获取文档切片列表 // 获取文档切片列表
const getDocumentSlices = async (page = 1, append = false) => { const getDocumentSlices = async (page = 1, append = false) => {
if (!props.documentId || loading.value) return; if (!props.documentId || loading.value) return;
try { try {
loading.value = true; loading.value = true;
// 获取文档信息(仅在第一页时获取) // 获取文档信息(仅在第一页时获取)
if (page === 1) { if (page === 1) {
const documentRes = await getDocument(props.documentId); const documentRes = await getDocument(props.documentId);
if (documentRes && documentRes.data) { if (documentRes && documentRes.data) {
documentTitle.value = documentRes.data.name || '未命名文档'; documentTitle.value = documentRes.data.name || '未命名文档';
} }
} }
// 获取该文档的切片(分页) // 获取该文档的切片(分页)
const { data } = await fetchList({ const { data } = await fetchList({
documentId: props.documentId, documentId: props.documentId,
current: page, current: page,
size: pageSize.value, size: pageSize.value,
}); });
if (data) { if (data) {
totalSlices.value = data.total; totalSlices.value = data.total;
// 处理切片数据 // 处理切片数据
const newSlices = data.records.sort((a: any, b: any) => { const newSlices = data.records.sort((a: any, b: any) => {
// 如果有顺序字段使用顺序字段否则按ID排序 // 如果有顺序字段使用顺序字段否则按ID排序
return (a.orderNum || 0) - (b.orderNum || 0); return (a.orderNum || 0) - (b.orderNum || 0);
}); });
// 如果返回的记录为空,设置没有更多数据标志 // 如果返回的记录为空,设置没有更多数据标志
if (newSlices.length === 0) { if (newSlices.length === 0) {
noMoreData.value = true; noMoreData.value = true;
return; return;
} }
// 追加或替换数据 // 追加或替换数据
if (append) { if (append) {
slices.value = [...slices.value, ...newSlices]; slices.value = [...slices.value, ...newSlices];
} else { } else {
slices.value = newSlices; slices.value = newSlices;
} }
// 检查是否还有更多数据 // 检查是否还有更多数据
noMoreData.value = slices.value.length >= totalSlices.value; noMoreData.value = slices.value.length >= totalSlices.value;
allSlicesLoaded.value = slices.value.length >= totalSlices.value; allSlicesLoaded.value = slices.value.length >= totalSlices.value;
} }
} catch (error: any) { } catch (error: any) {
useMessage().error(error.msg || '获取文档切片失败'); useMessage().error(error.msg || '获取文档切片失败');
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
// 加载更多数据 // 加载更多数据
const loadMoreSlices = () => { const loadMoreSlices = () => {
if (loading.value || noMoreData.value) return; if (loading.value || noMoreData.value) return;
currentPage.value++; currentPage.value++;
getDocumentSlices(currentPage.value, true); getDocumentSlices(currentPage.value, true);
}; };
// 滚动事件处理 // 滚动事件处理
const onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number, scrollHeight: number, clientHeight: number }) => { const onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number; scrollHeight: number; clientHeight: number }) => {
// 当滚动到底部附近时加载更多数据 // 当滚动到底部附近时加载更多数据
if (scrollHeight - scrollTop - clientHeight < 200 && !loading.value && !noMoreData.value) { if (scrollHeight - scrollTop - clientHeight < 200 && !loading.value && !noMoreData.value) {
loadMoreSlices(); loadMoreSlices();
} }
}; };
// 使用 useIntersectionObserver 监听加载触发器元素 // 使用 useIntersectionObserver 监听加载触发器元素
const { stop: stopObserver } = useIntersectionObserver( const { stop: stopObserver } = useIntersectionObserver(
loadTriggerRef, loadTriggerRef,
([{ isIntersecting }]) => { ([{ isIntersecting }]) => {
if (isIntersecting && !loading.value && !noMoreData.value && visible.value && slices.value.length > 0) { if (isIntersecting && !loading.value && !noMoreData.value && visible.value && slices.value.length > 0) {
loadMoreSlices(); loadMoreSlices();
} }
}, },
{ {
threshold: 0.1 threshold: 0.1,
} }
); );
// 组件卸载时停止观察 // 组件卸载时停止观察
onBeforeUnmount(() => { onBeforeUnmount(() => {
stopObserver(); stopObserver();
}); });
// 选择切片 // 选择切片
const selectSlice = (slice: any) => { const selectSlice = (slice: any) => {
if (selectedSliceId.value === slice.id) { if (selectedSliceId.value === slice.id) {
selectedSliceId.value = null; selectedSliceId.value = null;
selectedSlice.value = null; selectedSlice.value = null;
} else { } else {
selectedSliceId.value = slice.id; selectedSliceId.value = slice.id;
selectedSlice.value = slice; selectedSlice.value = slice;
} }
}; };
// 开始编辑 // 开始编辑
const startEditing = (slice: any) => { const startEditing = (slice: any) => {
editingSlice.value = slice; editingSlice.value = slice;
editingContent.value = slice.content; editingContent.value = slice.content;
nextTick(() => { nextTick(() => {
// Use setTimeout to ensure the DOM is updated // Use setTimeout to ensure the DOM is updated
setTimeout(() => { setTimeout(() => {
const textareaElement = document.querySelector('.slice-editor textarea'); const textareaElement = document.querySelector('.slice-editor textarea');
if (textareaElement) { if (textareaElement) {
(textareaElement as HTMLTextAreaElement).focus(); (textareaElement as HTMLTextAreaElement).focus();
} }
}, 0); }, 0);
}); });
}; };
// 保存编辑内容 // 保存编辑内容
const saveContent = async (slice: any) => { const saveContent = async (slice: any) => {
// 内容没有变动 // 内容没有变动
if (editingContent.value === slice.content) { if (editingContent.value === slice.content) {
editingSlice.value = null; editingSlice.value = null;
return; return;
} }
// 内容为空 // 内容为空
if (!editingContent.value.trim()) { if (!editingContent.value.trim()) {
useMessage().error('内容不能为空'); useMessage().error('内容不能为空');
editingContent.value = slice.content; editingContent.value = slice.content;
editingSlice.value = null; editingSlice.value = null;
return; return;
} }
try { try {
await putObj({ await putObj({
...slice, ...slice,
content: editingContent.value content: editingContent.value,
}); });
useMessage().success('修改成功'); useMessage().success('修改成功');
// 更新本地数据 // 更新本地数据
const index = slices.value.findIndex(s => s.id === slice.id); const index = slices.value.findIndex((s) => s.id === slice.id);
if (index !== -1) { if (index !== -1) {
slices.value[index].content = editingContent.value; slices.value[index].content = editingContent.value;
} }
// 如果是当前选中的切片,也更新选中的切片 // 如果是当前选中的切片,也更新选中的切片
if (selectedSlice.value && selectedSlice.value.id === slice.id) { if (selectedSlice.value && selectedSlice.value.id === slice.id) {
selectedSlice.value.content = editingContent.value; selectedSlice.value.content = editingContent.value;
} }
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} finally { } finally {
editingSlice.value = null; editingSlice.value = null;
} }
}; };
// 重新训练操作 // 重新训练操作
const handleRetrain = async (slice: any) => { const handleRetrain = async (slice: any) => {
try { try {
await useMessageBox().confirm('确认要重新训练该切片吗?'); await useMessageBox().confirm('确认要重新训练该切片吗?');
} catch { } catch {
return; return;
} }
try { try {
await putObj({ ...slice, sliceStatus: '0' }); await putObj({ ...slice, sliceStatus: '0' });
useMessage().success('已提交重新训练'); useMessage().success('已提交重新训练');
// 更新本地数据 // 更新本地数据
const index = slices.value.findIndex(s => s.id === slice.id); const index = slices.value.findIndex((s) => s.id === slice.id);
if (index !== -1) { if (index !== -1) {
slices.value[index].sliceStatus = '0'; slices.value[index].sliceStatus = '0';
} }
// 如果是当前选中的切片,也更新选中的切片 // 如果是当前选中的切片,也更新选中的切片
if (selectedSlice.value && selectedSlice.value.id === slice.id) { if (selectedSlice.value && selectedSlice.value.id === slice.id) {
selectedSlice.value.sliceStatus = '0'; selectedSlice.value.sliceStatus = '0';
} }
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
// 滚动到指定切片的位置 // 滚动到指定切片的位置
const scrollToSlice = async (sliceId: string) => { const scrollToSlice = async (sliceId: string) => {
// 如果没有找到切片,可能需要加载更多数据 // 如果没有找到切片,可能需要加载更多数据
if (!slices.value.some(s => s.id === sliceId) && !allSlicesLoaded.value) { if (!slices.value.some((s) => s.id === sliceId) && !allSlicesLoaded.value) {
// 重置并加载所有数据 // 重置并加载所有数据
currentPage.value = 1; currentPage.value = 1;
loading.value = true; loading.value = true;
try { try {
// 获取所有切片数据以确保能找到目标切片 // 获取所有切片数据以确保能找到目标切片
const { data } = await fetchList({ const { data } = await fetchList({
documentId: props.documentId, documentId: props.documentId,
size: 999, // 大数值以获取所有切片 size: 999, // 大数值以获取所有切片
}); });
if (data && data.records) { if (data && data.records) {
slices.value = data.records.sort((a: any, b: any) => { slices.value = data.records.sort((a: any, b: any) => {
return (a.orderNum || 0) - (b.orderNum || 0); return (a.orderNum || 0) - (b.orderNum || 0);
}); });
allSlicesLoaded.value = true; allSlicesLoaded.value = true;
noMoreData.value = true; noMoreData.value = true;
} }
} catch (error: any) { } catch (error: any) {
useMessage().error(error.msg || '获取文档切片失败'); useMessage().error(error.msg || '获取文档切片失败');
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
nextTick(() => { nextTick(() => {
const element = document.getElementById(`slice-${sliceId}`); const element = document.getElementById(`slice-${sliceId}`);
if (element) { if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' }); element.scrollIntoView({ behavior: 'smooth', block: 'center' });
} }
}); });
}; };
// 重置分页状态 // 重置分页状态
const resetPagination = () => { const resetPagination = () => {
currentPage.value = 1; currentPage.value = 1;
noMoreData.value = false; noMoreData.value = false;
allSlicesLoaded.value = false; allSlicesLoaded.value = false;
slices.value = []; slices.value = [];
}; };
// 监听 visible 变化 // 监听 visible 变化
watch(() => visible.value, (newVal) => { watch(
if (newVal && props.documentId) { () => visible.value,
resetPagination(); (newVal) => {
getDocumentSlices(); if (newVal && props.documentId) {
resetPagination();
if (props.sliceId) { getDocumentSlices();
selectedSliceId.value = props.sliceId;
scrollToSlice(props.sliceId);
}
} else if (!newVal) {
// 关闭抽屉时重置状态
activeTab.value = 'document';
imageError.value = '';
}
});
watch(() => props.documentId, (newVal) => { if (props.sliceId) {
if (newVal && visible.value) { selectedSliceId.value = props.sliceId;
resetPagination(); scrollToSlice(props.sliceId);
getDocumentSlices(); }
// 重置图片错误状态 } else if (!newVal) {
imageError.value = ''; // 关闭抽屉时重置状态
} activeTab.value = 'document';
}); imageError.value = '';
}
}
);
watch(() => props.sliceId, (newVal) => { watch(
if (newVal && visible.value) { () => props.documentId,
selectedSliceId.value = newVal; (newVal) => {
scrollToSlice(newVal); if (newVal && visible.value) {
} resetPagination();
}); getDocumentSlices();
// 重置图片错误状态
imageError.value = '';
}
}
);
watch(
() => props.sliceId,
(newVal) => {
if (newVal && visible.value) {
selectedSliceId.value = newVal;
scrollToSlice(newVal);
}
}
);
// Watch slices to update selectedSlice when data is loaded // Watch slices to update selectedSlice when data is loaded
watch(() => slices.value, (newSlices) => { watch(
if (selectedSliceId.value && newSlices.length > 0) { () => slices.value,
const slice = newSlices.find(s => s.id === selectedSliceId.value); (newSlices) => {
if (slice) { if (selectedSliceId.value && newSlices.length > 0) {
selectedSlice.value = slice; const slice = newSlices.find((s) => s.id === selectedSliceId.value);
} if (slice) {
} selectedSlice.value = slice;
}); }
}
</script> }
);
</script>

View File

@@ -1,109 +1,107 @@
<template> <template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" <el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
:close-on-click-modal="false" draggable> <el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading"> <el-form-item label="内容" prop="content">
<editor v-model:get-html="form.content" height="500" width="600" />
</el-form-item>
<el-form-item label="内容" prop="content"> </el-form>
<editor v-model:get-html="form.content" height="500" width="600"/> <template #footer>
</el-form-item> <span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</el-form> </span>
<template #footer> </template>
<span class="dialog-footer"> </el-dialog>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template> </template>
<script setup lang="ts" name="AiSliceDialog"> <script setup lang="ts" name="AiSliceDialog">
import {useDict} from '/@/hooks/dict'; import { useDict } from '/@/hooks/dict';
import {useMessage} from "/@/hooks/message"; import { useMessage } from '/@/hooks/message';
import {getObj, addObj, putObj} from '/@/api/knowledge/aiSlice' import { getObj, addObj, putObj } from '/@/api/knowledge/aiSlice';
import {rule} from '/@/utils/validate'; import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
// 定义变量内容 // 定义变量内容
const dataFormRef = ref(); const dataFormRef = ref();
const visible = ref(false) const visible = ref(false);
const loading = ref(false) const loading = ref(false);
// 定义字典 // 定义字典
// 提交表单数据 // 提交表单数据
const form = reactive({ const form = reactive({
id: '', id: '',
name: '', name: '',
units: '', units: '',
fileSize: '', fileSize: '',
hitCount: '', hitCount: '',
charCount: '', charCount: '',
content: '', content: '',
}); });
// 定义校验规则 // 定义校验规则
const dataRules = ref({ const dataRules = ref({
content: [{required: true, message: '内容不能为空', trigger: 'blur'}, { content: [
min: 100, { required: true, message: '内容不能为空', trigger: 'blur' },
max: 1500, {
message: '文本长度在 100 - 1500 之间', min: 100,
trigger: 'blur', max: 1500,
}] message: '文本长度在 100 - 1500 之间',
}) trigger: 'blur',
},
],
});
// 打开弹窗 // 打开弹窗
const openDialog = (id: string) => { const openDialog = (id: string) => {
visible.value = true visible.value = true;
form.id = '' form.id = '';
// 重置表单数据 // 重置表单数据
nextTick(() => { nextTick(() => {
dataFormRef.value?.resetFields(); dataFormRef.value?.resetFields();
}); });
// 获取aiSlice信息 // 获取aiSlice信息
if (id) { if (id) {
form.id = id form.id = id;
getaiSliceData(id) getaiSliceData(id);
} }
}; };
// 提交 // 提交
const onSubmit = async () => { const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => { const valid = await dataFormRef.value.validate().catch(() => {});
}); if (!valid) return false;
if (!valid) return false;
try { try {
loading.value = true; loading.value = true;
form.id ? await putObj(form) : await addObj(form); form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功'); useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false; visible.value = false;
emit('refresh'); emit('refresh');
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
// 初始化表单数据 // 初始化表单数据
const getaiSliceData = (id: string) => { const getaiSliceData = (id: string) => {
// 获取数据 // 获取数据
loading.value = true loading.value = true;
getObj(id).then((res: any) => { getObj(id)
Object.assign(form, res.data) .then((res: any) => {
}).finally(() => { Object.assign(form, res.data);
loading.value = false })
}) .finally(() => {
loading.value = false;
});
}; };
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
openDialog openDialog,
}); });
</script> </script>

View File

@@ -269,7 +269,7 @@ const saveContent = async (slice: any) => {
try { try {
await putObj({ await putObj({
...slice, ...slice,
content: editingContent.value content: editingContent.value,
}); });
useMessage().success('修改成功'); useMessage().success('修改成功');
getDataList(); getDataList();

View File

@@ -1,33 +1,24 @@
<template> <template>
<transition name="el-zoom-in-center"> <transition name="el-zoom-in-center">
<div <div v-show="isShow" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" :style="`top: ${y}px; left: ${x}px;`">
v-show="isShow" <ul class="el-dropdown-menu">
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" <li v-for="item in menuItems" :key="item.text" class="el-dropdown-menu__item" @click="handleItemClick(item)">
:style="`top: ${y}px; left: ${x}px;`" <el-icon><component :is="item.icon" /></el-icon>
> <span>{{ item.text }}</span>
<ul class="el-dropdown-menu"> </li>
<li </ul>
v-for="item in menuItems" <div class="el-popper__arrow" :style="{ left: '10px' }"></div>
:key="item.text" </div>
class="el-dropdown-menu__item" </transition>
@click="handleItemClick(item)"
>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.text }}</span>
</li>
</ul>
<div class="el-popper__arrow" :style="{ left: '10px' }"></div>
</div>
</transition>
</template> </template>
<script setup> <script setup>
import { ref, defineProps, defineEmits } from 'vue'; import { ref, defineProps, defineEmits } from 'vue';
const props = defineProps({ const props = defineProps({
x: Number, x: Number,
y: Number, y: Number,
menuItems: Array, menuItems: Array,
}); });
const emit = defineEmits(['itemClick', 'close']); const emit = defineEmits(['itemClick', 'close']);
@@ -35,16 +26,16 @@ const emit = defineEmits(['itemClick', 'close']);
const isShow = ref(false); const isShow = ref(false);
const handleItemClick = (item) => { const handleItemClick = (item) => {
emit('itemClick', item); emit('itemClick', item);
isShow.value = false; isShow.value = false;
}; };
const show = () => { const show = () => {
isShow.value = true; isShow.value = true;
}; };
const hide = () => { const hide = () => {
isShow.value = false; isShow.value = false;
}; };
defineExpose({ show, hide }); defineExpose({ show, hide });
@@ -52,28 +43,28 @@ defineExpose({ show, hide });
<style scoped> <style scoped>
.custom-contextmenu { .custom-contextmenu {
position: fixed; position: fixed;
z-index: 2190; z-index: 2190;
min-width: 120px; min-width: 120px;
background-color: #fff; background-color: #fff;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
} }
.el-dropdown-menu__item { .el-dropdown-menu__item {
font-size: 14px; font-size: 14px;
padding: 8px 16px; padding: 8px 16px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.el-dropdown-menu__item:hover { .el-dropdown-menu__item:hover {
background-color: #f5f7fa; background-color: #f5f7fa;
} }
.el-icon { .el-icon {
margin-right: 8px; margin-right: 8px;
} }
</style> </style>

View File

@@ -1,38 +1,28 @@
<template> <template>
<el-dialog <el-dialog v-model="dialogVisible" title="标记信息" width="30%" :close-on-click-modal="false">
v-model="dialogVisible" <el-form :model="form" :rules="rules" ref="formRef">
title="标记信息" <el-form-item prop="name" label="字段名" :label-width="formLabelWidth">
width="30%" <el-input v-model="form.name" autocomplete="off"></el-input>
:close-on-click-modal="false" </el-form-item>
> <el-form-item prop="description" label="字段描述" :label-width="formLabelWidth">
<el-form :model="form" :rules="rules" ref="formRef"> <el-input v-model="form.description" type="textarea" :rows="4" placeholder="请输入描述"></el-input>
<el-form-item prop="name" label="字段名" :label-width="formLabelWidth"> </el-form-item>
<el-input v-model="form.name" autocomplete="off"></el-input> </el-form>
</el-form-item> <template #footer>
<el-form-item prop="description" label="字段描述" :label-width="formLabelWidth"> <span class="dialog-footer">
<el-input <el-button @click="closeDialog">取消</el-button>
v-model="form.description" <el-button type="primary" @click="submitForm">确认</el-button>
type="textarea" </span>
:rows="4" </template>
placeholder="请输入描述" </el-dialog>
></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="submitForm">确认</el-button>
</span>
</template>
</el-dialog>
</template> </template>
<script setup> <script setup>
import {rule} from "/@/utils/validate"; import { rule } from '/@/utils/validate';
const props = defineProps({ const props = defineProps({
initialName: String, initialName: String,
initialDescription: String, initialDescription: String,
}); });
const emit = defineEmits(['submit', 'close']); const emit = defineEmits(['submit', 'close']);
@@ -40,45 +30,45 @@ const emit = defineEmits(['submit', 'close']);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const formRef = ref(null); const formRef = ref(null);
const form = reactive({ const form = reactive({
name: props.initialName || '', name: props.initialName || '',
description: props.initialDescription || '' description: props.initialDescription || '',
}); });
const rules = { const rules = {
name: [ name: [
{required: true, message: '请输入属性名', trigger: 'blur'}, { required: true, message: '请输入属性名', trigger: 'blur' },
{validator: rule.overLength, trigger: 'blur'}, { validator: rule.overLength, trigger: 'blur' },
{validator: rule.validatorLowercase, trigger: 'blur'} { validator: rule.validatorLowercase, trigger: 'blur' },
], ],
description: [ description: [
{required: true, message: '请输入描述', trigger: 'blur'}, { required: true, message: '请输入描述', trigger: 'blur' },
{validator: rule.overLength, trigger: 'blur'}, { validator: rule.overLength, trigger: 'blur' },
] ],
}; };
const formLabelWidth = '80px'; const formLabelWidth = '80px';
const submitForm = () => { const submitForm = () => {
formRef.value.validate((valid) => { formRef.value.validate((valid) => {
if (valid) { if (valid) {
emit('submit', {name: form.name, description: form.description}); emit('submit', { name: form.name, description: form.description });
closeDialog(); closeDialog();
} else { } else {
return false; return false;
} }
}); });
}; };
const closeDialog = () => { const closeDialog = () => {
dialogVisible.value = false; dialogVisible.value = false;
emit('close'); emit('close');
}; };
const showDialog = (name, description) => { const showDialog = (name, description) => {
form.name = name || ''; form.name = name || '';
form.description = description || ''; form.description = description || '';
dialogVisible.value = true; dialogVisible.value = true;
}; };
defineExpose({showDialog}); defineExpose({ showDialog });
</script> </script>

View File

@@ -1,110 +1,108 @@
<template> <template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" <el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
:close-on-click-modal="false" draggable> <el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading"> <el-form-item label="标题" prop="ocrTitle">
<el-form-item label="标题" prop="ocrTitle"> <el-input v-model="form.ocrTitle" placeholder="请输入标题" />
<el-input v-model="form.ocrTitle" placeholder="请输入标题"/> </el-form-item>
</el-form-item>
<el-form-item label="描述" prop="ocrPrompt"> <el-form-item label="描述" prop="ocrPrompt">
<el-input type="textarea" :rows="5" v-model="form.ocrPrompt" placeholder="请输入描述提示词"/> <el-input type="textarea" :rows="5" v-model="form.ocrPrompt" placeholder="请输入描述提示词" />
</el-form-item> </el-form-item>
</el-form>
</el-form> <template #footer>
<template #footer> <span class="dialog-footer">
<span class="dialog-footer"> <el-button @click="visible = false"> </el-button>
<el-button @click="visible = false"> </el-button> <el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button> </span>
</span> </template>
</template> </el-dialog>
</el-dialog>
</template> </template>
<script setup lang="ts" name="AiOcrConfDialog"> <script setup lang="ts" name="AiOcrConfDialog">
import {useDict} from '/@/hooks/dict'; import { useDict } from '/@/hooks/dict';
import {useMessage} from "/@/hooks/message"; import { useMessage } from '/@/hooks/message';
import {getObj, addObj, putObj, validateExist} from '/@/api/knowledge/ocr' import { getObj, addObj, putObj, validateExist } from '/@/api/knowledge/ocr';
import {rule} from '/@/utils/validate'; import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
// 定义变量内容 // 定义变量内容
const dataFormRef = ref(); const dataFormRef = ref();
const visible = ref(false) const visible = ref(false);
const loading = ref(false) const loading = ref(false);
// 定义字典 // 定义字典
// 提交表单数据 // 提交表单数据
const form = reactive({ const form = reactive({
id: '', id: '',
ocrTitle: '', ocrTitle: '',
ocrPrompt: '', ocrPrompt: '',
imageResource: '', imageResource: '',
ocrMarked: '', ocrMarked: '',
}); });
// 定义校验规则 // 定义校验规则
const dataRules = { const dataRules = {
ocrTitle: [ ocrTitle: [
{required: true, message: '请输入功能名称', trigger: 'blur'}, { required: true, message: '请输入功能名称', trigger: 'blur' },
{validator: rule.overLength, trigger: 'blur'} { validator: rule.overLength, trigger: 'blur' },
], ],
ocrPrompt: [ ocrPrompt: [
{required: true, message: '请输入提示词描述', trigger: 'blur'}, { required: true, message: '请输入提示词描述', trigger: 'blur' },
{validator: rule.overLength, trigger: 'blur'}, { validator: rule.overLength, trigger: 'blur' },
] ],
}; };
// 打开弹窗 // 打开弹窗
const openDialog = (id: string) => { const openDialog = (id: string) => {
visible.value = true visible.value = true;
form.id = '' form.id = '';
// 重置表单数据 // 重置表单数据
nextTick(() => { nextTick(() => {
dataFormRef.value?.resetFields(); dataFormRef.value?.resetFields();
}); });
// 获取aiOcrConf信息 // 获取aiOcrConf信息
if (id) { if (id) {
form.id = id form.id = id;
getAiOcrConfData(id) getAiOcrConfData(id);
} }
}; };
// 提交 // 提交
const onSubmit = async () => { const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => { const valid = await dataFormRef.value.validate().catch(() => {});
}); if (!valid) return false;
if (!valid) return false;
try { try {
loading.value = true; loading.value = true;
form.id ? await putObj(form) : await addObj(form); form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功'); useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false; visible.value = false;
emit('refresh'); emit('refresh');
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
// 初始化表单数据 // 初始化表单数据
const getAiOcrConfData = (id: string) => { const getAiOcrConfData = (id: string) => {
// 获取数据 // 获取数据
loading.value = true loading.value = true;
getObj({id: id}).then((res: any) => { getObj({ id: id })
Object.assign(form, res.data[0]) .then((res: any) => {
}).finally(() => { Object.assign(form, res.data[0]);
loading.value = false })
}) .finally(() => {
loading.value = false;
});
}; };
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
openDialog openDialog,
}); });
</script> </script>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts" name="authredirect"> <script setup lang="ts" name="authredirect">
import request from '/@/utils/request'; import request from '/@/utils/request';
import other from '/@/utils/other'; import other from '/@/utils/other';
import {validateNull} from '/@/utils/validate'; import { validateNull } from '/@/utils/validate';
import {Session} from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import {useUserInfo} from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
import {useMessageBox} from '/@/hooks/message'; import { useMessageBox } from '/@/hooks/message';
/** /**
* 加载完成后执行的函数,用于处理授权回调。 * 加载完成后执行的函数,用于处理授权回调。

View File

@@ -1,89 +1,85 @@
<template> <template>
<el-form size="large" class="login-content-form" :rules="dataRules" ref="dataFormRef" :model="passwordFormData"> <el-form size="large" class="login-content-form" :rules="dataRules" ref="dataFormRef" :model="passwordFormData">
<el-form-item class="login-animation1" prop="username"> <el-form-item class="login-animation1" prop="username">
<el-input text :placeholder="$t('password.accountPlaceholder1')" disabled v-model="passwordFormData.username" <el-input text :placeholder="$t('password.accountPlaceholder1')" disabled v-model="passwordFormData.username" clearable autocomplete="off">
clearable <template #prefix>
autocomplete="off"> <el-icon class="el-input__icon">
<template #prefix> <ele-User />
<el-icon class="el-input__icon"> </el-icon>
<ele-User/> </template>
</el-icon> </el-input>
</template> </el-form-item>
</el-input> <el-form-item class="login-animation1" prop="password">
</el-form-item> <el-input
<el-form-item class="login-animation1" prop="password"> text
<el-input text :placeholder="$t('expire.oldPassword')" v-model="passwordFormData.password" clearable :placeholder="$t('expire.oldPassword')"
:type="showPassword ? 'text' : 'password'" v-model="passwordFormData.password"
autocomplete="off"> clearable
<template #prefix> :type="showPassword ? 'text' : 'password'"
<el-icon class="el-input__icon"> autocomplete="off"
<ele-Unlock/> >
</el-icon> <template #prefix>
</template> <el-icon class="el-input__icon">
<template #suffix> <ele-Unlock />
<i </el-icon>
class="iconfont el-input__icon login-content-password" </template>
:class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'" <template #suffix>
@click="showPassword = !showPassword" <i
> class="iconfont el-input__icon login-content-password"
</i> :class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
</template> @click="showPassword = !showPassword"
</el-input> >
</el-form-item> </i>
<el-form-item class="login-animation2" prop="newpassword1"> </template>
<strength-meter </el-input>
:placeholder="$t('expire.newPassword')" </el-form-item>
v-model="passwordFormData.newpassword1" <el-form-item class="login-animation2" prop="newpassword1">
autocomplete="off" <strength-meter
:maxLength="20" :placeholder="$t('expire.newPassword')"
:minLength="6" v-model="passwordFormData.newpassword1"
@score="handlePassScore" autocomplete="off"
> :maxLength="20"
<template #prefix> :minLength="6"
<el-icon class="el-input__icon"> @score="handlePassScore"
<ele-Unlock/> >
</el-icon> <template #prefix>
</template> <el-icon class="el-input__icon">
</strength-meter> <ele-Unlock />
</el-form-item> </el-icon>
</template>
</strength-meter>
</el-form-item>
<el-form-item class="login-animation2" prop="newpassword2"> <el-form-item class="login-animation2" prop="newpassword2">
<strength-meter <strength-meter
:placeholder="$t('expire.confirmPassword')" :placeholder="$t('expire.confirmPassword')"
v-model="passwordFormData.newpassword2" v-model="passwordFormData.newpassword2"
autocomplete="off" autocomplete="off"
:maxLength="20" :maxLength="20"
:minLength="6" :minLength="6"
@score="handlePassScore" @score="handlePassScore"
> >
<template #prefix> <template #prefix>
<el-icon class="el-input__icon"> <el-icon class="el-input__icon">
<ele-Unlock/> <ele-Unlock />
</el-icon> </el-icon>
</template> </template>
</strength-meter> </strength-meter>
</el-form-item> </el-form-item>
<el-form-item class="login-animation4"> <el-form-item class="login-animation4">
<el-button <el-button type="primary" class="rounded-lg login-content-submit" v-waves @click="handleResetPassword" :loading="loading">
type="primary" <span class="font-semibold tracking-wide">{{ $t('password.resetBtnText') }}</span>
class="rounded-lg login-content-submit" </el-button>
v-waves </el-form-item>
@click="handleResetPassword" </el-form>
:loading="loading"
>
<span class="font-semibold tracking-wide">{{ $t('password.resetBtnText') }}</span>
</el-button>
</el-form-item>
</el-form>
</template> </template>
<script setup lang="ts" name="expire"> <script setup lang="ts" name="expire">
import {resetUserPassword} from '/@/api/admin/user'; import { resetUserPassword } from '/@/api/admin/user';
import {useMessage} from '/@/hooks/message'; import { useMessage } from '/@/hooks/message';
import {useI18n} from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import {LoginTypeEnum} from "/@/api/login"; import { LoginTypeEnum } from '/@/api/login';
import type { FormInstance } from 'element-plus'; import type { FormInstance } from 'element-plus';
// 注册生命周期事件 // 注册生命周期事件
@@ -93,7 +89,7 @@ const emit = defineEmits(['afterSuccess', 'change']);
const StrengthMeter = defineAsyncComponent(() => import('/@/components/StrengthMeter/index.vue')); const StrengthMeter = defineAsyncComponent(() => import('/@/components/StrengthMeter/index.vue'));
// 使用i18n // 使用i18n
const {t} = useI18n(); const { t } = useI18n();
// 表单引用 // 表单引用
const dataFormRef = ref<FormInstance | null>(null); const dataFormRef = ref<FormInstance | null>(null);
@@ -105,59 +101,59 @@ const loading = ref(false);
const score = ref('0'); const score = ref('0');
const props = defineProps({ const props = defineProps({
// 当前的值 // 当前的值
username: String, username: String,
}); });
const showPassword = ref(false); const showPassword = ref(false);
const passwordFormData = reactive({ const passwordFormData = reactive({
username: props.username, username: props.username,
password: '', password: '',
newpassword1: '', newpassword1: '',
newpassword2: '', newpassword2: '',
}); });
const validatorPassword2 = (rule: any, value: any, callback: any) => { const validatorPassword2 = (rule: any, value: any, callback: any) => {
if (value !== passwordFormData.newpassword1) { if (value !== passwordFormData.newpassword1) {
callback(new Error(t('expire.passwordRule'))); callback(new Error(t('expire.passwordRule')));
} else { } else {
callback(); callback();
} }
}; };
const validatorScore = (rule: any, value: any, callback: any) => { const validatorScore = (rule: any, value: any, callback: any) => {
if (Number(score.value) <= 1) { if (Number(score.value) <= 1) {
callback(new Error(t('expire.passwordScore'))); callback(new Error(t('expire.passwordScore')));
} else { } else {
callback(); callback();
} }
}; };
// 表单验证规则 // 表单验证规则
const dataRules = reactive({ const dataRules = reactive({
password: [{required: true, message: t('expire.oldPassword'), trigger: 'blur'}], password: [{ required: true, message: t('expire.oldPassword'), trigger: 'blur' }],
newpassword1: [ newpassword1: [
{ {
min: 6, min: 6,
max: 20, max: 20,
message: t('register.passwordLength'), message: t('register.passwordLength'),
trigger: 'blur', trigger: 'blur',
}, },
{validator: validatorScore, trigger: 'blur'}, { validator: validatorScore, trigger: 'blur' },
], ],
newpassword2: [ newpassword2: [
{ {
min: 6, min: 6,
max: 20, max: 20,
message: t('register.passwordLength'), message: t('register.passwordLength'),
trigger: 'blur', trigger: 'blur',
}, },
{validator: validatorPassword2, trigger: 'blur'}, { validator: validatorPassword2, trigger: 'blur' },
] ],
}); });
// 处理密码强度得分变化事件 // 处理密码强度得分变化事件
const handlePassScore = (e: string) => { const handlePassScore = (e: string) => {
score.value = e; score.value = e;
}; };
/** /**
@@ -165,21 +161,21 @@ const handlePassScore = (e: string) => {
* @description 重置密码 * @description 重置密码
*/ */
const handleResetPassword = async () => { const handleResetPassword = async () => {
if (!dataFormRef.value) return false; if (!dataFormRef.value) return false;
// 验证表单是否符合规则
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
try { // 验证表单是否符合规则
loading.value = true; const valid = await dataFormRef.value.validate().catch(() => {});
await resetUserPassword(passwordFormData); if (!valid) return false;
useMessage().success(t('common.optSuccessText'));
emit('change', LoginTypeEnum.PASSWORD); try {
} catch (err: any) { loading.value = true;
useMessage().error(err.msg); await resetUserPassword(passwordFormData);
} finally { useMessage().success(t('common.optSuccessText'));
loading.value = false; emit('change', LoginTypeEnum.PASSWORD);
} } catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
}; };
</script> </script>

View File

@@ -64,14 +64,14 @@
> >
{{ $t('mobile.backToLogin') }} {{ $t('mobile.backToLogin') }}
</a> </a>
<!-- <a--> <!-- <a-->
<!-- href="#"--> <!-- href="#"-->
<!-- v-if="autoRegisterEnable"--> <!-- v-if="autoRegisterEnable"-->
<!-- class="ml-2 text-blue-500 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300"--> <!-- class="ml-2 text-blue-500 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300"-->
<!-- @click="emit('change', LoginTypeEnum.REGISTER)"--> <!-- @click="emit('change', LoginTypeEnum.REGISTER)"-->
<!-- >--> <!-- >-->
<!-- {{ $t('mobile.createAccount') }}--> <!-- {{ $t('mobile.createAccount') }}-->
<!-- </a>--> <!-- </a>-->
</div> </div>
</div> </div>

View File

@@ -67,9 +67,9 @@
<a href="#" class="text-blue-500" @click="emit('change', LoginTypeEnum.MOBILE)"> <a href="#" class="text-blue-500" @click="emit('change', LoginTypeEnum.MOBILE)">
{{ $t('password.mobileLogin') }} {{ $t('password.mobileLogin') }}
</a> </a>
<!-- <a href="#" v-if="autoRegisterEnable" class="ml-2 text-blue-500" @click="emit('change', LoginTypeEnum.REGISTER)">--> <!-- <a href="#" v-if="autoRegisterEnable" class="ml-2 text-blue-500" @click="emit('change', LoginTypeEnum.REGISTER)">-->
<!-- {{ $t('password.createAccount') }}--> <!-- {{ $t('password.createAccount') }}-->
<!-- </a>--> <!-- </a>-->
</div> </div>
</div> </div>

View File

@@ -1,18 +1,18 @@
<template> <template>
<div class="fixed bottom-0 right-0 z-10 flex flex-col items-center mb-16 w-[160px]"> <div class="fixed bottom-0 right-0 z-10 flex flex-col items-center mb-16 w-[160px]">
<!-- <div class="group">--> <!-- <div class="group">-->
<!-- <div class="relative transition-transform duration-300 transform group-hover:scale-110 group-hover:-translate-y-1">--> <!-- <div class="relative transition-transform duration-300 transform group-hover:scale-110 group-hover:-translate-y-1">-->
<!-- <img--> <!-- <img-->
<!-- class="w-24 h-24 rounded-lg ring-gray-200 dark:ring-slate-700 dark:bg-slate-800"--> <!-- class="w-24 h-24 rounded-lg ring-gray-200 dark:ring-slate-700 dark:bg-slate-800"-->
<!-- :src="!themeConfig.miniQr ? miniQr : baseURL + themeConfig.miniQr"--> <!-- :src="!themeConfig.miniQr ? miniQr : baseURL + themeConfig.miniQr"-->
<!-- :alt="t('scan.wechatApp')"--> <!-- :alt="t('scan.wechatApp')"-->
<!-- />--> <!-- />-->
<!-- </div>--> <!-- </div>-->
<!-- &lt;!&ndash; 底部文字 &ndash;&gt;--> <!-- &lt;!&ndash; 底部文字 &ndash;&gt;-->
<!-- <div class="mt-2">--> <!-- <div class="mt-2">-->
<!-- <p class="text-xs text-gray-400 dark:text-slate-500">{{ t('scan.wechatApp') }}</p>--> <!-- <p class="text-xs text-gray-400 dark:text-slate-500">{{ t('scan.wechatApp') }}</p>-->
<!-- </div>--> <!-- </div>-->
<!-- </div>--> <!-- </div>-->
</div> </div>
</template> </template>

View File

@@ -131,7 +131,7 @@ onMounted(async () => {
// 获取当前租户名称 // 获取当前租户名称
const getCurrentTenantName = computed(() => { const getCurrentTenantName = computed(() => {
const current = tenantList.value.find(item => item.id === tenant.value); const current = tenantList.value.find((item) => item.id === tenant.value);
return current?.name || t('tenantSelect.select'); return current?.name || t('tenantSelect.select');
}); });
</script> </script>

View File

@@ -37,14 +37,14 @@
<expire v-if="loginType === LoginTypeEnum.EXPIRE" :username="username" @change="changeLoginType" /> <expire v-if="loginType === LoginTypeEnum.EXPIRE" :username="username" @change="changeLoginType" />
<!-- 分割线 --> <!-- 分割线 -->
<!-- <div class="flex justify-center items-center my-6 space-x-3">--> <!-- <div class="flex justify-center items-center my-6 space-x-3">-->
<!-- <span class="w-20 h-[1.5px] bg-gray-200 dark:bg-slate-600"></span>--> <!-- <span class="w-20 h-[1.5px] bg-gray-200 dark:bg-slate-600"></span>-->
<!-- <span class="text-gray-600 dark:text-slate-400">{{ $t('divider.or') }}</span>--> <!-- <span class="text-gray-600 dark:text-slate-400">{{ $t('divider.or') }}</span>-->
<!-- <span class="w-20 h-[1.5px] bg-gray-200 dark:bg-slate-600"></span>--> <!-- <span class="w-20 h-[1.5px] bg-gray-200 dark:bg-slate-600"></span>-->
<!-- </div>--> <!-- </div>-->
<!-- 社交登录 --> <!-- 社交登录 -->
<!-- <social @signInSuccess="signInSuccess" />--> <!-- <social @signInSuccess="signInSuccess" />-->
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,258 +1,261 @@
<template> <template>
<div> <div>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
:disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20" v-if="!hiddenFields.type">
<el-col :span="12" class="mb20" v-if="!hiddenFields.type"> <el-form-item :label="t('askLeave.type')" prop="type">
<el-form-item :label="t('askLeave.type')" prop="type"> <el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type" />
<el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type"/> </el-form-item>
</el-form-item> </el-col>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.startTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.startTime">
<el-form-item :label="t('askLeave.startTime')" prop="startTime"> <el-form-item :label="t('askLeave.startTime')" prop="startTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputStartTimeTip')" :disabled="disabledFields.startTime" <el-date-picker
v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputStartTimeTip')"
</el-col> :disabled="disabledFields.startTime"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.endTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.endTime">
<el-form-item :label="t('askLeave.endTime')" prop="endTime"> <el-form-item :label="t('askLeave.endTime')" prop="endTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputEndTimeTip')" :disabled="disabledFields.endTime" <el-date-picker
v-model="form.endTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputEndTimeTip')"
</el-col> :disabled="disabledFields.endTime"
v-model="form.endTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.days"> <el-col :span="12" class="mb20" v-if="!hiddenFields.days">
<el-form-item :label="t('askLeave.days')" prop="days"> <el-form-item :label="t('askLeave.days')" prop="days">
<el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days"/> <el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson"> <el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson">
<el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson"> <el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" :disabled="disabledFields.carbonCopyPerson" <el-select
remote :remote-method="remoteMethod" :reserve-keyword="false" v-model="form.carbonCopyPerson"
clearable filterable multiple> :placeholder="t('askLeave.inputCarbonCopyPersonTip')"
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :disabled="disabledFields.carbonCopyPerson"
:value="item.userId"> remote
:remote-method="remoteMethod"
:reserve-keyword="false"
clearable
filterable
multiple
>
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"> </el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
</el-option> <el-col :span="12" class="mb20" v-if="!hiddenFields.remark">
</el-select> <el-form-item :label="t('askLeave.remark')" prop="remark">
</el-tooltip> <el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.remark"> <el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls">
<el-form-item :label="t('askLeave.remark')" prop="remark"> <el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls">
<el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark"/> <el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-form>
<template v-if="data.submitBtn">
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{ t('jfI18n.print') }} </el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }}</el-button>
</span>
</footer>
</template>
<template v-else>
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{ t('jfI18n.print') }} </el-button>
</span>
</footer>
</template>
<el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls"> <!-- 打印表单 -->
<el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls"> <el-dialog v-model="data.showTinymceView" top="20px" width="700px" :title="data.tinymceTitle" append-to-body @close="closePrint">
<el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls"/> <tinymce-view v-if="data.showTinymceView" :currFlowForm="form" :elTab="data.elTab"></tinymce-view>
</el-form-item> </el-dialog>
</el-col> </div>
</el-row>
</el-form>
<template v-if="data.submitBtn">
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{
t('jfI18n.print')
}}
</el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }}</el-button>
</span>
</footer>
</template>
<template v-else>
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{
t('jfI18n.print')
}}
</el-button>
</span>
</footer>
</template>
<!-- 打印表单 -->
<el-dialog v-model="data.showTinymceView" top="20px" width="700px"
:title="data.tinymceTitle" append-to-body
@close="closePrint">
<tinymce-view v-if="data.showTinymceView" :currFlowForm="form" :elTab="data.elTab"></tinymce-view>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="AskLeaveForm"> <script setup lang="ts" name="AskLeaveForm">
import { useMessage } from '/@/hooks/message';
import * as askLeave from '/@/api/order/ask-leave';
import { useI18n } from 'vue-i18n';
import { onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert.ts';
import * as orderVue from '/@/api/order/order-key-vue';
import { handleCustomFormPerm, handleFormPrint } from '/@/flow/utils/form-perm';
import { deepClone } from '/@/utils/other';
import { initCustomFormMethods } from '../index';
import {useMessage} from "/@/hooks/message"; const { t } = useI18n();
import * as askLeave from '/@/api/order/ask-leave' const emits = defineEmits(['handleJob']);
import {useI18n} from "vue-i18n" // 引入组件
import {onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey} from "/@/flow/components/convert-name/convert.ts"; const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue'));
import * as orderVue from "/@/api/order/order-key-vue";
import {handleCustomFormPerm, handleFormPrint} from "/@/flow/utils/form-perm";
import {deepClone} from "/@/utils/other";
import {initCustomFormMethods} from "../index";
const {t} = useI18n(); // 定义变量内容
const emits = defineEmits(["handleJob"]); const dataFormRef = ref();
// 引入组件 const loading = ref(false);
const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue')); const operType = ref(false);
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'carbonCopyPerson' });
onMounted(async () => {
// await onLoad(dicData);
});
// 定义变量内容 function remoteMethod(query: string) {
const dataFormRef = ref(); remoteMethodByKey(query, onLoad, dicData, 'userName', 'carbonCopyPerson');
const loading = ref(false); }
const operType = ref(false);
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "carbonCopyPerson"});
onMounted(async () => {
// await onLoad(dicData);
});
function remoteMethod(query: string) { const props = defineProps({
remoteMethodByKey(query, onLoad, dicData, 'userName', "carbonCopyPerson") currJob: {
} type: Object,
default: null,
},
currElTab: {
type: Object,
default: {},
},
});
const props = defineProps({ // 提交表单数据
currJob: { const form = reactive({
type: Object, type: '',
default: null, startTime: '',
}, endTime: '',
currElTab: { days: '',
type: Object, carbonCopyPerson: [],
default: {}, remark: '',
}, imgUrls: '',
}); });
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
type: '', startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
startTime: '', days: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
endTime: '', remark: [{ required: true, message: '请假事由不能为空', trigger: 'blur' }],
days: '', });
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 定义校验规则 function initJobData() {
const dataRules = ref({ handleGetObj(props.currJob.orderId);
startTime: [{required: true, message: '开始时间不能为空', trigger: 'blur'}], }
days: [{required: true, message: '请假天数不能为空', trigger: 'blur'}],
remark: [{required: true, message: '请假事由不能为空', trigger: 'blur'}],
})
function initJobData() { const fieldsPerm = {
handleGetObj(props.currJob.orderId) type: false,
} startTime: false,
endTime: false,
days: false,
carbonCopyPerson: false,
remark: false,
imgUrls: false,
};
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
const fieldsPerm = { function handleGetObj(id) {
type: false, askLeave.getObj(id).then(async (resp) => {
startTime: false, let formData = resp.data ? resp.data : {};
endTime: false, Object.assign(form, formData);
days: false, await onFormLoaded(dicData, form);
carbonCopyPerson: false, form.runJobId = props.currJob.id;
remark: false, await initFormPermPrint();
imgUrls: false, });
} }
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
function handleGetObj(id) { const data = reactive({
askLeave.getObj(id).then(async resp => { showTinymceView: false,
let formData = resp.data ? resp.data : {} tinymceTitle: '',
Object.assign(form, formData) submitBtn: true,
await onFormLoaded(dicData, form); elTab: null,
form.runJobId = props.currJob.id });
await initFormPermPrint()
})
}
const data = reactive({ const methods = initCustomFormMethods(data, disabledFields, operType);
showTinymceView: false,
tinymceTitle: '',
submitBtn: true,
elTab: null
})
const methods = initCustomFormMethods(data, disabledFields, operType) async function initFormPermPrint() {
let elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id);
// 处理表单权限
let res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab);
// 判断是否仅查看
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback);
// 采用elTab配置的模板
await handleFormPrint(form, elTab.type, elTab.id, '1');
data.elTab = elTab;
}
async function initFormPermPrint() { function printForm() {
let elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id); closePrint(true, false);
// 处理表单权限 data.tinymceTitle = '请假工单';
let res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab) data.showTinymceView = true;
// 判断是否仅查看 }
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback)
// 采用elTab配置的模板
await handleFormPrint(form, elTab.type, elTab.id, '1')
data.elTab = elTab
}
function printForm() { function closePrint(isInit, isSave) {
closePrint(true, false) if (isInit) {
data.tinymceTitle = '请假工单' form.formData = form;
data.showTinymceView = true form.dicData = { carbonCopyPerson: dicData.carbonCopyPerson };
} form['carbonCopyPerson.valueKey'] = 'userId';
form['carbonCopyPerson.showKey'] = 'name';
} else {
delete form.formData;
delete form.dicData;
delete form['carbonCopyPerson.valueKey'];
delete form['carbonCopyPerson.showKey'];
}
if (isSave) delete form.printInfo;
}
function closePrint(isInit, isSave){ async function submitForm() {
if (isInit) { closePrint(false, true);
form.formData = form try {
form.dicData = {carbonCopyPerson: dicData.carbonCopyPerson} loading.value = true;
form['carbonCopyPerson.valueKey'] = 'userId' await askLeave.putObj(form);
form['carbonCopyPerson.showKey'] = 'name' orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits);
} else { useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
delete form.formData } catch (err: any) {
delete form.dicData useMessage().error(err.msg);
delete form['carbonCopyPerson.valueKey'] } finally {
delete form['carbonCopyPerson.showKey'] loading.value = false;
} }
if (isSave) delete form.printInfo }
}
async function submitForm() { // 监听双向绑定
closePrint(false, true) watch(
try { () => props.currJob.id,
loading.value = true; () => {
await askLeave.putObj(form) initJobData();
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits) }
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); );
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
}
// 监听双向绑定 onMounted(() => {
watch( initJobData();
() => props.currJob.id, });
() => {
initJobData();
}
);
onMounted(() => {
initJobData()
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,307 +1,314 @@
<template> <template>
<div> <div>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'"> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="12" class="mb20" v-if="!hiddenFields.type"> <el-col :span="12" class="mb20" v-if="!hiddenFields.type">
<el-form-item :label="t('askLeave.type')" prop="type"> <el-form-item :label="t('askLeave.type')" prop="type">
<el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type"/> <el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.startTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.startTime">
<el-form-item :label="t('askLeave.startTime')" prop="startTime"> <el-form-item :label="t('askLeave.startTime')" prop="startTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputStartTimeTip')" :disabled="disabledFields.startTime" <el-date-picker
v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputStartTimeTip')"
</el-col> :disabled="disabledFields.startTime"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.endTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.endTime">
<el-form-item :label="t('askLeave.endTime')" prop="endTime"> <el-form-item :label="t('askLeave.endTime')" prop="endTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputEndTimeTip')" :disabled="disabledFields.endTime" <el-date-picker
v-model="form.endTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputEndTimeTip')"
</el-col> :disabled="disabledFields.endTime"
v-model="form.endTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.days"> <el-col :span="12" class="mb20" v-if="!hiddenFields.days">
<el-form-item :label="t('askLeave.days')" prop="days"> <el-form-item :label="t('askLeave.days')" prop="days">
<el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days"/> <el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson"> <el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson">
<el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson"> <el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" clearable filterable multiple <el-select
remote :remote-method="remoteMethod" :reserve-keyword="false" v-model="form.carbonCopyPerson"
:disabled="disabledFields.carbonCopyPerson"> :placeholder="t('askLeave.inputCarbonCopyPersonTip')"
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option> clearable
</el-select> filterable
</el-tooltip> multiple
</el-form-item> remote
</el-col> :remote-method="remoteMethod"
:reserve-keyword="false"
:disabled="disabledFields.carbonCopyPerson"
>
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.remark"> <el-col :span="12" class="mb20" v-if="!hiddenFields.remark">
<el-form-item :label="t('askLeave.remark')" prop="remark"> <el-form-item :label="t('askLeave.remark')" prop="remark">
<el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark"/> <el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls"> <el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls">
<el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls"> <el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls">
<el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls"/> <el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-row> </el-form>
</el-form> <template v-if="operType !== 'view'">
<template v-if="operType !== 'view'"> <div style="text-align: center">
<div style="text-align: center"> <span class="dialog-footer">
<span class="dialog-footer"> <template v-if="form.status !== DIC_PROP.ORDER_STATUS[0].value">
<template v-if="form.status !== DIC_PROP.ORDER_STATUS[0].value"> <el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('jfI18n.initialBtn') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('jfI18n.initialBtn') }}</el-button> <el-button @click="onRefresh()">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="onRefresh()">{{ $t('common.cancelButtonText') }}</el-button> <el-button type="primary" @click="onTemp" :disabled="loading">{{ $t('jfI18n.temp') }}</el-button>
<el-button type="primary" @click="onTemp" :disabled="loading">{{ $t('jfI18n.temp') }}</el-button> </template>
</template> <template v-else>
<template v-else> <el-button type="primary" @click="printForm" v-if="form.printInfo">{{ t('jfI18n.print') }} </el-button>
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{ <el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.editBtn') }}</el-button>
t('jfI18n.print') </template>
}} </span>
</el-button> </div>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.editBtn') }}</el-button> </template>
</template> <template v-if="operType === 'view'">
</span> <footer class="el-dialog__footer">
</div> <div style="text-align: center">
</template> <span class="dialog-footer">
<template v-if="operType === 'view'"> <el-button type="primary" @click="printForm" v-if="form.printInfo">{{ t('jfI18n.print') }} </el-button>
<footer class="el-dialog__footer"> </span>
<div style="text-align: center"> </div>
<span class="dialog-footer"> </footer>
<el-button type="primary" @click="printForm" v-if="form.printInfo">{{ </template>
t('jfI18n.print') <!-- 打印表单 -->
}} <el-dialog v-model="data.showTinymceView" top="20px" width="700px" :title="data.tinymceTitle" append-to-body @close="closePrint">
</el-button> <tinymce-view v-if="data.showTinymceView" :currFlowForm="form" :elTab="data.elTab"></tinymce-view>
</span> </el-dialog>
</div> </div>
</footer>
</template>
<!-- 打印表单 -->
<el-dialog v-model="data.showTinymceView" top="20px" width="700px"
:title="data.tinymceTitle" append-to-body
@close="closePrint">
<tinymce-view v-if="data.showTinymceView" :currFlowForm="form" :elTab="data.elTab"></tinymce-view>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="AskLeaveDialog"> <script setup lang="ts" name="AskLeaveDialog">
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, tempStore } from '/@/api/order/ask-leave';
import { useI18n } from 'vue-i18n';
import { rule, validateNull } from '/@/utils/validate';
import { onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert.ts';
import * as common from '/@/flow/support/common';
import { handleFormPrint, handleFormStartPerm } from '/@/flow/utils/form-perm';
import { currFormIsView, orderKeyMap } from '/@/api/order/order-key-vue';
import { deepClone } from '/@/utils/other';
import { DIC_PROP } from '/@/flow/support/dict-prop';
import { initCustomFormMethods } from '../index';
import { useMessage } from "/@/hooks/message"; const emit = defineEmits(['refresh']);
import {getObj, addObj, putObj, tempStore} from '/@/api/order/ask-leave' const { t } = useI18n();
import { useI18n } from "vue-i18n" // 引入组件
import {rule, validateNull} from '/@/utils/validate'; const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue'));
import {onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey} from "/@/flow/components/convert-name/convert.ts";
import * as common from '/@/flow/support/common'
import {handleFormPrint, handleFormStartPerm} from "/@/flow/utils/form-perm";
import {currFormIsView, orderKeyMap} from "/@/api/order/order-key-vue";
import {deepClone} from "/@/utils/other";
import {DIC_PROP} from "/@/flow/support/dict-prop";
import {initCustomFormMethods} from "../index";
const emit = defineEmits(['refresh']); // 定义变量内容
const { t } = useI18n(); const dataFormRef = ref();
// 引入组件 const visible = ref(false);
const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue')); const loading = ref(false);
const operType = ref(false);
const title = ref('');
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'carbonCopyPerson' });
onMounted(async () => {
// await onLoad(dicData);
await openDialog(props.formData.type, props.formData.id);
});
// 定义变量内容 const props = defineProps({
const dataFormRef = ref(); formData: {
const visible = ref(false); type: Object,
const loading = ref(false); default: null,
const operType = ref(false); },
const title = ref(''); });
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "carbonCopyPerson"});
onMounted(async () => {
// await onLoad(dicData);
await openDialog(props.formData.type, props.formData.id)
});
const props = defineProps({ // 提交表单数据
formData: { const form = reactive({
type: Object, type: '',
default: null, startTime: '',
} endTime: '',
}); days: '',
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
type: '', startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
startTime: '', days: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
endTime: '', remark: [{ required: true, message: '请假事由不能为空', trigger: 'blur' }],
days: '', });
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 定义校验规则 const fieldsPerm = {
const dataRules = ref({ type: false,
startTime: [{required: true, message: '开始时间不能为空', trigger: 'blur'}], startTime: false,
days: [{required: true, message: '请假天数不能为空', trigger: 'blur'}], endTime: false,
remark: [{required: true, message: '请假事由不能为空', trigger: 'blur'}], days: false,
}) carbonCopyPerson: false,
remark: false,
imgUrls: false,
};
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
const fieldsPerm = { // 打开弹窗
type: false, const openDialog = async (type: string, id: string) => {
startTime: false, visible.value = true;
endTime: false, operType.value = type;
days: false, form.id = '';
carbonCopyPerson: false,
remark: false,
imgUrls: false,
}
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
// 打开弹窗 if (type === 'add') {
const openDialog = async (type: string, id: string) => { title.value = t('common.addBtn');
visible.value = true } else if (type === 'edit') {
operType.value = type; title.value = t('common.editBtn');
form.id = '' } else if (type === 'view') {
title.value = t('common.viewBtn');
} else if (type === 'copy') {
title.value = t('common.copyBtn');
}
if (type === 'add') { // 重置表单数据
title.value = t('common.addBtn'); nextTick(async () => {
} else if (type === 'edit') { dataFormRef.value?.resetFields();
title.value = t('common.editBtn'); // 获取AskLeave信息
} else if (type === 'view') { if (id) {
title.value = t('common.viewBtn'); form.id = id;
} else if (type === 'copy') { await getAskLeaveData(id);
title.value = t('common.copyBtn'); }
} await initFormPermPrint();
});
};
// 重置表单数据 const data = reactive({
nextTick(async () => { showTinymceView: false,
dataFormRef.value?.resetFields(); tinymceTitle: '',
// 获取AskLeave信息 elTab: null,
if (id) { });
form.id = id
await getAskLeaveData(id)
}
await initFormPermPrint()
});
}; const methods = initCustomFormMethods(data, disabledFields);
const data = reactive({ async function initFormPermPrint() {
showTinymceView: false, // 处理表单权限 开始节点必须配置表单
tinymceTitle: '', let res = await handleFormStartPerm(hiddenFields, disabledFields, null, null, orderKeyMap.AskLeave, null);
elTab: null await currFormIsView(methods, res.elTab, true, res.callback);
}) // 采用elTab配置的模板
await handleFormPrint(form, res.elTab.type, res.elTab.id, '1');
data.elTab = res.elTab;
}
const methods = initCustomFormMethods(data, disabledFields) function printForm() {
closePrint(true, false);
data.tinymceTitle = '请假工单';
data.showTinymceView = true;
}
async function initFormPermPrint() { function closePrint(isInit, isSave) {
// 处理表单权限 开始节点必须配置表单 if (isInit) {
let res = await handleFormStartPerm(hiddenFields, disabledFields, null, null, orderKeyMap.AskLeave, null); form.formData = form;
await currFormIsView(methods, res.elTab, true, res.callback) form.dicData = { carbonCopyPerson: dicData.carbonCopyPerson };
// 采用elTab配置的模板 form['carbonCopyPerson.valueKey'] = 'userId';
await handleFormPrint(form, res.elTab.type, res.elTab.id, '1') form['carbonCopyPerson.showKey'] = 'name';
data.elTab = res.elTab } else {
} delete form.formData;
delete form.dicData;
delete form['carbonCopyPerson.valueKey'];
delete form['carbonCopyPerson.showKey'];
}
if (isSave) delete form.printInfo;
}
function printForm() { // 暂存
closePrint(true, false) const onTemp = async () => {
data.tinymceTitle = '请假工单' closePrint(false, true);
data.showTinymceView = true
}
function closePrint(isInit, isSave){ let clone = { operType: operType.value, form: form };
if (isInit) { common.handleCloneSubmit(clone);
form.formData = form try {
form.dicData = {carbonCopyPerson: dicData.carbonCopyPerson} loading.value = true;
form['carbonCopyPerson.valueKey'] = 'userId' await tempStore(form);
form['carbonCopyPerson.showKey'] = 'name' useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
} else { visible.value = false;
delete form.formData onRefresh();
delete form.dicData } catch (err: any) {
delete form['carbonCopyPerson.valueKey'] useMessage().error(err.msg);
delete form['carbonCopyPerson.showKey'] } finally {
} loading.value = false;
if (isSave) delete form.printInfo }
} };
// 暂存 function remoteMethod(query: string) {
const onTemp = async () => { remoteMethodByKey(query, onLoad, dicData, 'userName', 'carbonCopyPerson');
closePrint(false, true) }
let clone = {operType: operType.value, form: form}; // 提交
common.handleCloneSubmit(clone); const onSubmit = async () => {
try { const valid = await dataFormRef.value.validate().catch(() => {});
loading.value = true; if (!valid) return false;
await tempStore(form); closePrint(false, true);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
onRefresh();
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
}
function remoteMethod(query: string) { let clone = { operType: operType.value, form: form };
remoteMethodByKey(query, onLoad, dicData, 'userName', "carbonCopyPerson") common.handleCloneSubmit(clone);
} try {
loading.value = true;
form.status !== DIC_PROP.ORDER_STATUS[0].value ? await addObj(form) : await putObj(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
onRefresh();
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 提交 const onRefresh = () => {
const onSubmit = async () => { emit('refresh');
const valid = await dataFormRef.value.validate().catch(() => {}); };
if (!valid) return false;
closePrint(false, true)
let clone = {operType: operType.value, form: form}; // 初始化表单数据
common.handleCloneSubmit(clone) const getAskLeaveData = async (id: string) => {
try { // 获取数据
loading.value = true; loading.value = true;
form.status !== DIC_PROP.ORDER_STATUS[0].value ? await addObj(form) : await putObj(form); let res = await getObj(id);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); loading.value = false;
visible.value = false; Object.assign(form, res.data);
onRefresh(); await onFormLoaded(dicData, form);
} catch (err: any) { let clone = { operType: operType.value, form: form };
useMessage().error(err.msg); common.handleClone(clone);
} finally { };
loading.value = false;
}
};
const onRefresh = () => { async function getFormData() {
emit('refresh'); const valid = await dataFormRef.value.validate().catch(() => {});
} if (!valid) return false;
return form;
}
// 初始化表单数据 // 暴露变量
const getAskLeaveData = async (id: string) => { defineExpose({
// 获取数据 openDialog,
loading.value = true getFormData,
let res = await getObj(id) });
loading.value = false
Object.assign(form, res.data)
await onFormLoaded(dicData, form);
let clone = {operType: operType.value, form: form};
common.handleClone(clone);
};
async function getFormData() {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
return form
}
// 暴露变量
defineExpose({
openDialog,
getFormData
});
</script> </script>

View File

@@ -1,44 +1,43 @@
export default { export default {
askLeave: { askLeave: {
index: '#', index: '#',
importaskLeaveTip: 'import AskLeave', importaskLeaveTip: 'import AskLeave',
id: 'id', id: 'id',
code: 'code', code: 'code',
flowKey: 'flowKey', flowKey: 'flowKey',
type: 'type', type: 'type',
startTime: 'startTime', startTime: 'startTime',
endTime: 'endTime', endTime: 'endTime',
days: 'days', days: 'days',
carbonCopyPerson: 'carbonCopyPerson', carbonCopyPerson: 'carbonCopyPerson',
remark: 'remark', remark: 'remark',
status: 'status', status: 'status',
finishTime: 'finishTime', finishTime: 'finishTime',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
flowInstId: 'flowInstId', flowInstId: 'flowInstId',
imgUrls: 'imgUrls', imgUrls: 'imgUrls',
updateUser: 'updateUser', updateUser: 'updateUser',
updateTime: 'updateTime', updateTime: 'updateTime',
delFlag: 'delFlag', delFlag: 'delFlag',
inputIdTip: 'input id', inputIdTip: 'input id',
inputCodeTip: 'input code', inputCodeTip: 'input code',
inputFlowKeyTip: 'input flowKey', inputFlowKeyTip: 'input flowKey',
inputTypeTip: 'input type', inputTypeTip: 'input type',
inputStartTimeTip: 'input startTime', inputStartTimeTip: 'input startTime',
inputEndTimeTip: 'input endTime', inputEndTimeTip: 'input endTime',
inputDaysTip: 'input days', inputDaysTip: 'input days',
inputCarbonCopyPersonTip: 'input carbonCopyPerson', inputCarbonCopyPersonTip: 'input carbonCopyPerson',
inputRemarkTip: 'input remark', inputRemarkTip: 'input remark',
inputStatusTip: 'input status', inputStatusTip: 'input status',
inputFinishTimeTip: 'input finishTime', inputFinishTimeTip: 'input finishTime',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputFlowInstIdTip: 'input flowInstId', inputFlowInstIdTip: 'input flowInstId',
inputImgUrlsTip: 'input imgUrls', inputImgUrlsTip: 'input imgUrls',
inputUpdateUserTip: 'input updateUser', inputUpdateUserTip: 'input updateUser',
inputUpdateTimeTip: 'input updateTime', inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag', inputDelFlagTip: 'input delFlag',
},
} };
}

View File

@@ -1,44 +1,43 @@
export default { export default {
askLeave: { askLeave: {
index: '#', index: '#',
importaskLeaveTip: '导入请假工单', importaskLeaveTip: '导入请假工单',
id: '主键id', id: '主键id',
code: '工单编号', code: '工单编号',
flowKey: '流程KEY', flowKey: '流程KEY',
type: '请假类型', type: '请假类型',
startTime: '开始时间', startTime: '开始时间',
endTime: '结束时间', endTime: '结束时间',
days: '请假天数', days: '请假天数',
carbonCopyPerson: '抄送人', carbonCopyPerson: '抄送人',
remark: '请假事由', remark: '请假事由',
status: '状态', status: '状态',
finishTime: '完成时间', finishTime: '完成时间',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
flowInstId: '流程实例ID', flowInstId: '流程实例ID',
imgUrls: '上传文档', imgUrls: '上传文档',
updateUser: '修改人', updateUser: '修改人',
updateTime: '修改时间', updateTime: '修改时间',
delFlag: '删除标', delFlag: '删除标',
inputIdTip: '请输入主键id', inputIdTip: '请输入主键id',
inputCodeTip: '请输入编号', inputCodeTip: '请输入编号',
inputFlowKeyTip: '请输入流程KEY', inputFlowKeyTip: '请输入流程KEY',
inputTypeTip: '请输入请假类型', inputTypeTip: '请输入请假类型',
inputStartTimeTip: '请输入开始时间', inputStartTimeTip: '请输入开始时间',
inputEndTimeTip: '请输入结束时间', inputEndTimeTip: '请输入结束时间',
inputDaysTip: '请输入请假天数', inputDaysTip: '请输入请假天数',
inputCarbonCopyPersonTip: '请输入抄送人', inputCarbonCopyPersonTip: '请输入抄送人',
inputRemarkTip: '请输入请假事由', inputRemarkTip: '请输入请假事由',
inputStatusTip: '请输入状态', inputStatusTip: '请输入状态',
inputFinishTimeTip: '请输入完成时间', inputFinishTimeTip: '请输入完成时间',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputFlowInstIdTip: '请输入流程实例ID', inputFlowInstIdTip: '请输入流程实例ID',
inputImgUrlsTip: '请输入上传文档', inputImgUrlsTip: '请输入上传文档',
inputUpdateUserTip: '请输入修改人', inputUpdateUserTip: '请输入修改人',
inputUpdateTimeTip: '请输入修改时间', inputUpdateTimeTip: '请输入修改时间',
inputDelFlagTip: '请输入删除标', inputDelFlagTip: '请输入删除标',
},
} };
}

View File

@@ -1,304 +1,303 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('askLeave.code')" prop="code" > <el-form-item :label="$t('askLeave.code')" prop="code">
<el-input :placeholder="t('askLeave.inputCodeTip')" v-model="state.queryForm.code" clearable <el-input :placeholder="t('askLeave.inputCodeTip')" v-model="state.queryForm.code" clearable style="max-width: 180px" />
style="max-width: 180px" /> </el-form-item>
</el-form-item> <el-form-item :label="$t('askLeave.status')" prop="status">
<el-form-item :label="$t('askLeave.status')" prop="status"> <el-select v-model="state.queryForm.status" :placeholder="t('askLeave.inputStatusTip')" clearable filterable style="max-width: 180px">
<el-select v-model="state.queryForm.status" :placeholder="t('askLeave.inputStatusTip')" clearable filterable style="max-width: 180px"> <el-option v-for="(item, index) in DIC_PROP.ORDER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
<el-option v-for="(item, index) in DIC_PROP.ORDER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> </el-select>
</el-select> </el-form-item>
</el-form-item> <el-form-item>
<el-form-item> <el-button icon="search" type="primary" @click="getDataList">
<el-button icon="search" type="primary" @click="getDataList"> {{ $t('common.queryBtn') }}
{{ $t('common.queryBtn') }} </el-button>
</el-button> <el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }} </el-button>
<el-button icon="Refresh" @click="resetQuery">{{ </el-form-item>
$t('common.resetBtn') </el-form>
}} </el-row>
</el-button> <el-row>
</el-form-item> <div class="mb8" style="width: 100%">
</el-form> <el-tooltip placement="top">
</el-row> <template #content>
<el-row> {{ $t('common.addBtn') }}
<div class="mb8" style="width: 100%"> </template>
<el-button icon="Plus" type="primary" class="ml10" @click="openDialog('add')" v-auth="'order_askleave_add'"> </el-button>
</el-tooltip>
<el-tooltip placement="top">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
class="ml10"
v-auth="'order_askleave_del'"
@click="handleDelete(selectObjs)"
>
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <right-toolbar
<template #content> v-model:showSearch="showSearch"
{{ $t('common.addBtn') }} :export="'order_askleave_export'"
</template> @exportExcel="exportExcel"
<el-button icon="Plus" type="primary" class="ml10" @click="openDialog('add')" class="ml10"
v-auth="'order_askleave_add'"> style="float: right; margin-right: 20px"
</el-button> @queryTable="getDataList"
</el-tooltip> ></right-toolbar>
<el-tooltip placement="top"> </div>
<template #content> </el-row>
{{ $t('common.delBtn') }} <el-table
</template> :data="state.dataList"
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" v-loading="state.loading"
v-auth="'order_askleave_del'" @click="handleDelete(selectObjs)"> style="width: 100%"
</el-button> @selection-change="handleSelectionChange"
</el-tooltip> @sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" :label="t('askLeave.index')" width="40" />
<el-table-column prop="code" :label="t('askLeave.code')" show-overflow-tooltip />
<el-table-column prop="flowKey" :label="t('askLeave.flowKey')" show-overflow-tooltip />
<el-table-column prop="type" :label="t('askLeave.type')" show-overflow-tooltip />
<el-table-column prop="startTime" :label="t('askLeave.startTime')" show-overflow-tooltip />
<el-table-column prop="endTime" :label="t('askLeave.endTime')" show-overflow-tooltip />
<el-table-column prop="days" :label="t('askLeave.days')" show-overflow-tooltip />
<el-table-column prop="carbonCopyPerson" :label="t('askLeave.carbonCopyPerson')" show-overflow-tooltip>
<template #default="scope">
<convert-name
:options="state.dicData.carbonCopyPerson"
:value="scope.row.carbonCopyPerson"
:valueKey="'userId'"
:showKey="'name'"
></convert-name>
</template>
</el-table-column>
<el-table-column prop="remark" :label="t('askLeave.remark')" show-overflow-tooltip />
<el-table-column prop="status" :label="t('askLeave.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.ORDER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="finishTime" :label="t('askLeave.finishTime')" show-overflow-tooltip />
<el-table-column prop="createUser" :label="t('askLeave.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('askLeave.createTime')" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-tooltip placement="top">
<template #content>
{{ $t('common.viewBtn') }}
</template>
<el-button text type="primary" icon="view" @click="openDialog('view', scope.row.id)"> </el-button>
</el-tooltip>
<right-toolbar v-model:showSearch="showSearch" :export="'order_askleave_export'" <el-tooltip placement="top">
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" <template #content> 打印表单 </template>
@queryTable="getDataList"></right-toolbar> <el-button icon="Document" text type="primary" @click="openDialog('view', scope.row.id)"> </el-button>
</div> </el-tooltip>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center"/>
<el-table-column type="index" :label="t('askLeave.index')" width="40"/>
<el-table-column prop="code" :label="t('askLeave.code')" show-overflow-tooltip/>
<el-table-column prop="flowKey" :label="t('askLeave.flowKey')" show-overflow-tooltip/>
<el-table-column prop="type" :label="t('askLeave.type')" show-overflow-tooltip/>
<el-table-column prop="startTime" :label="t('askLeave.startTime')" show-overflow-tooltip/>
<el-table-column prop="endTime" :label="t('askLeave.endTime')" show-overflow-tooltip/>
<el-table-column prop="days" :label="t('askLeave.days')" show-overflow-tooltip/>
<el-table-column prop="carbonCopyPerson" :label="t('askLeave.carbonCopyPerson')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.carbonCopyPerson" :value="scope.row.carbonCopyPerson"
:valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="remark" :label="t('askLeave.remark')" show-overflow-tooltip/>
<el-table-column prop="status" :label="t('askLeave.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.ORDER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="finishTime" :label="t('askLeave.finishTime')" show-overflow-tooltip/>
<el-table-column prop="createUser" :label="t('askLeave.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser"
:valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('askLeave.createTime')" show-overflow-tooltip/>
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-tooltip placement="top">
<template #content>
{{ $t('common.viewBtn') }}
</template>
<el-button text type="primary" icon="view" @click="openDialog('view', scope.row.id)">
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top">
<template #content> <template #content>
打印表单 {{ $t('common.copyBtn') }}
</template> </template>
<el-button icon="Document" text type="primary" @click="openDialog('view', scope.row.id)"> <el-button icon="DocumentCopy" text type="primary" @click="openDialog('copy', scope.row.id, scope.row)"> </el-button>
</el-button> </el-tooltip>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[2].value">
<template #content> <template #content>
{{ $t('common.copyBtn') }} {{ $t('jfI18n.recallBtn') }}
</template> </template>
<el-button icon="DocumentCopy" text type="primary" @click="openDialog('copy', scope.row.id, scope.row)"> <el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
</el-button> </el-tooltip>
</el-tooltip> <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[0].value">
<template #content>
{{ $t('jfI18n.resetBtn') }}
</template>
<el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[2].value"> <el-tooltip placement="top" v-if="scope.row.flowInstId">
<template #content> <template #content>
{{ $t('jfI18n.recallBtn') }} {{ $t('jfI18n.viewFlow') }}
</template> </template>
<el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> <el-button text type="primary" icon="Share" @click="openPreview(scope.row)"></el-button>
</el-button> </el-tooltip>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[0].value">
<template #content>
{{ $t('jfI18n.resetBtn') }}
</template>
<el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)">
</el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.flowInstId"> <el-tooltip
<template #content> placement="top"
{{ $t('jfI18n.viewFlow') }} v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value || scope.row.status === DIC_PROP.ORDER_STATUS[0].value"
</template> >
<el-button text type="primary" icon="Share" @click="openPreview(scope.row)"></el-button> <template #content>
</el-tooltip> {{ $t('common.editBtn') }}
</template>
<el-button icon="edit-pen" text type="primary" @click="openDialog('edit', scope.row.id, scope.row)"> </el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value || scope.row.status===DIC_PROP.ORDER_STATUS[0].value"> <!-- 发起工单 -->
<template #content> <json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="refresh">
{{ $t('common.editBtn') }} <template v-slot="slotProps" v-if="showInitiateOrder">
</template> <form-dialog ref="form" v-show="slotProps.currActive === 'form'" :formData="formData" @refresh="refresh" />
<el-button icon="edit-pen" text type="primary" @click="openDialog('edit', scope.row.id, scope.row)"> </template>
</el-button> </json-flow-predict>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">
</el-button>
</el-tooltip>
</template> <!-- 编辑新增 -->
</el-table-column> <el-dialog title="请假工单" v-model="showFormDialog" width="60%" :close-on-click-modal="false" draggable>
</el-table> <form-dialog v-if="showFormDialog" ref="formDialogRef" :formData="formData" @refresh="refresh" />
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" </el-dialog>
v-bind="state.pagination"/> </div>
</div>
<!-- 发起工单 -->
<json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="refresh">
<template v-slot="slotProps" v-if="showInitiateOrder">
<form-dialog ref="form" v-show="slotProps.currActive === 'form'" :formData="formData" @refresh="refresh"/>
</template>
</json-flow-predict>
<!-- 编辑新增 -->
<el-dialog title="请假工单" v-model="showFormDialog" width="60%"
:close-on-click-modal="false" draggable>
<form-dialog v-if="showFormDialog" ref="formDialogRef" :formData="formData" @refresh="refresh"/>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="systemAskLeave"> <script setup lang="ts" name="systemAskLeave">
import {BasicTableProps, useTable} from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import {fetchList, delObjs} from "/@/api/order/ask-leave"; import { fetchList, delObjs } from '/@/api/order/ask-leave';
import {useMessage, useMessageBox} from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onCascadeChange, onLoadDicUrl, onLoaded} from "/@/flow/components/convert-name/convert"; import { onCascadeChange, onLoadDicUrl, onLoaded } from '/@/flow/components/convert-name/convert';
import {recallReset} from "/@/api/jsonflow/run-flow"; import { recallReset } from '/@/api/jsonflow/run-flow';
import {openFlowPreview} from "/@/flow/support/extend"; import { openFlowPreview } from '/@/flow/support/extend';
import {DIC_PROP} from "/@/flow/support/dict-prop"; import { DIC_PROP } from '/@/flow/support/dict-prop';
import {orderKeyMap} from "/@/api/order/order-key-vue"; import { orderKeyMap } from '/@/api/order/order-key-vue';
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue')); const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue'));
const {t} = useI18n() const { t } = useI18n();
const {proxy} = getCurrentInstance(); const { proxy } = getCurrentInstance();
// 定义查询字典 // 定义查询字典
const dicData = reactive({}); const dicData = reactive({});
// const onLoad = onLoadDicUrl(); // const onLoad = onLoadDicUrl();
onMounted(() => { onMounted(() => {
// onLoad(dicData); // onLoad(dicData);
}); });
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
const showFormDialog = ref(false) const showFormDialog = ref(false);
const showInitiateOrder = ref(false) const showInitiateOrder = ref(false);
const formData = ref({}) const formData = ref({});
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: fetchList, pageList: fetchList,
onLoaded: onLoaded({key: "createUser"}, {key: "carbonCopyPerson"}), onLoaded: onLoaded({ key: 'createUser' }, { key: 'carbonCopyPerson' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/ask-leave/export', state.queryForm, 'ask-leave.xlsx') downBlobFile('/order/ask-leave/export', state.queryForm, 'ask-leave.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await delObjs(ids); await delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
function handleRecallReset(row) { function handleRecallReset(row) {
let params = {id: row.flowInstId, flowKey: row.flowKey, status: row.status} let params = { id: row.flowInstId, flowKey: row.flowKey, status: row.status };
if (row.status === DIC_PROP.ORDER_STATUS[0].value) { if (row.status === DIC_PROP.ORDER_STATUS[0].value) {
recallReset(params).then(() => { recallReset(params).then(() => {
useMessage().success('重发成功'); useMessage().success('重发成功');
getDataList(); getDataList();
}) });
return return;
} }
useMessageBox().confirm('是否确认要撤回该工单?') useMessageBox()
.then(() => { .confirm('是否确认要撤回该工单?')
return recallReset(params) .then(() => {
}).then(() => { return recallReset(params);
useMessage().success('撤回成功') })
getDataList(); .then(() => {
}) useMessage().success('撤回成功');
} getDataList();
});
}
const $router = useRouter(); const $router = useRouter();
function openPreview(row) { function openPreview(row) {
openFlowPreview($router, {flowInstId: row.flowInstId}, '1') openFlowPreview($router, { flowInstId: row.flowInstId }, '1');
} }
function openPredict(row, bool) { function openPredict(row, bool) {
proxy.$refs.predict.open(row, bool) proxy.$refs.predict.open(row, bool);
} }
// 打开弹窗 // 打开弹窗
const openDialog = (type?: string, id?: string, row?) => { const openDialog = (type?: string, id?: string, row?) => {
formData.value = {type, id} formData.value = { type, id };
if (type === 'add' || type === 'edit' || type === 'copy') { if (type === 'add' || type === 'edit' || type === 'copy') {
showInitiateOrder.value = true showInitiateOrder.value = true;
if (type === 'add') { if (type === 'add') {
openPredict({flowKey: orderKeyMap.AskLeave}, true) openPredict({ flowKey: orderKeyMap.AskLeave }, true);
} else { } else {
openPredict(row, true) openPredict(row, true);
} }
} else { } else {
showFormDialog.value = true showFormDialog.value = true;
} }
} };
function refresh() {
getDataList(false)
openPredict({}, false)
showInitiateOrder.value = false
showFormDialog.value = false
}
function refresh() {
getDataList(false);
openPredict({}, false);
showInitiateOrder.value = false;
showFormDialog.value = false;
}
</script> </script>

View File

@@ -1,88 +1,87 @@
<template> <template>
<el-dialog :title="title" v-model="visible" width="80%" append-to-body <el-dialog :title="title" v-model="visible" width="80%" append-to-body :close-on-click-modal="false" draggable @closed="onCancel">
:close-on-click-modal="false" draggable @closed="onCancel"> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading" :disabled="operType === 'view'">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading" :disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20">
<el-col :span="12" class="mb20"> <el-form-item :label="t('createTable.tableName')" prop="tableName">
<el-form-item :label="t('createTable.tableName')" prop="tableName"> <el-input v-model="form.tableName" :placeholder="t('createTable.inputTableNameTip')" />
<el-input v-model="form.tableName" :placeholder="t('createTable.inputTableNameTip')"/> </el-form-item>
</el-form-item> </el-col>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.comments')" prop="comments"> <el-form-item :label="t('createTable.comments')" prop="comments">
<el-input v-model="form.comments" :placeholder="t('createTable.inputCommentsTip')"/> <el-input v-model="form.comments" :placeholder="t('createTable.inputCommentsTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.databaseType')" prop="databaseType"> <el-form-item :label="t('createTable.databaseType')" prop="databaseType">
<el-select v-model="form.databaseType" :placeholder="t('createTable.databaseType')"> <el-select v-model="form.databaseType" :placeholder="t('createTable.databaseType')">
<el-option v-for="(item, index) in DIC_PROP.DATABASE_TYPE" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.DATABASE_TYPE" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.pkPolicy')" prop="pkPolicy"> <el-form-item :label="t('createTable.pkPolicy')" prop="pkPolicy">
<el-select v-model="form.pkPolicy" :placeholder="t('createTable.inputPkPolicyTip')" clearable> <el-select v-model="form.pkPolicy" :placeholder="t('createTable.inputPkPolicyTip')" clearable>
<el-option label="ID_WORKER(分布式自增)" value="ID_WORKER"></el-option> <el-option label="ID_WORKER(分布式自增)" value="ID_WORKER"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.primaryKey')" prop="primaryKey"> <el-form-item :label="t('createTable.primaryKey')" prop="primaryKey">
<el-select v-model="form.primaryKey" :placeholder="t('createTable.inputPrimaryKeyTip')" clearable> <el-select v-model="form.primaryKey" :placeholder="t('createTable.inputPrimaryKeyTip')" clearable>
<el-option v-for="(item, index) in form.columns" :key="index" :label="item.name" :value="item.name"></el-option> <el-option v-for="(item, index) in form.columns" :key="index" :label="item.name" :value="item.name"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" class="mb20"> <el-col :span="24" class="mb20">
<el-form-item :label="t('createTable.columnInfo')" prop="columns"> <el-form-item :label="t('createTable.columnInfo')" prop="columns">
<el-table :data="form.columns" border style="width: 100%" max-height="500"> <el-table :data="form.columns" border style="width: 100%" max-height="500">
<el-table-column type="index" :label="t('createTable.index')" width="80"> <el-table-column type="index" :label="t('createTable.index')" width="80">
<template #header> <template #header>
<el-button icon="Plus" size="small" type="primary" circle @click="onAddItem"></el-button> <el-button icon="Plus" size="small" type="primary" circle @click="onAddItem"></el-button>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-button icon="Minus" size="small" type="danger" circle @click="handleDelete(scope.$index, scope.row)"></el-button> <el-button icon="Minus" size="small" type="danger" circle @click="handleDelete(scope.$index, scope.row)"></el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="name" :label="t('createTable.name')" show-overflow-tooltip> <el-table-column prop="name" :label="t('createTable.name')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.name" :placeholder="t('createTable.name')"/> <el-input v-model="scope.row.name" :placeholder="t('createTable.name')" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="comment" :label="t('createTable.comment')" show-overflow-tooltip> <el-table-column prop="comment" :label="t('createTable.comment')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.comment" :placeholder="t('createTable.comment')"/> <el-input v-model="scope.row.comment" :placeholder="t('createTable.comment')" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="typeName" :label="t('createTable.typeName')" show-overflow-tooltip> <el-table-column prop="typeName" :label="t('createTable.typeName')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.typeName" :placeholder="t('createTable.typeName')" clearable filterable> <el-select v-model="scope.row.typeName" :placeholder="t('createTable.typeName')" clearable filterable>
<el-option v-for="(item, index) in DIC_PROP.COLUMN_TYPE" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.COLUMN_TYPE" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="precision" :label="t('createTable.precision')" show-overflow-tooltip> <el-table-column prop="precision" :label="t('createTable.precision')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.precision" :placeholder="t('createTable.precision')"></el-input-number> <el-input-number :min="0" :max="10000" v-model="scope.row.precision" :placeholder="t('createTable.precision')"></el-input-number>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="scale" :label="t('createTable.scale')" show-overflow-tooltip> <el-table-column prop="scale" :label="t('createTable.scale')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.scale" :placeholder="t('createTable.scale')"></el-input-number> <el-input-number :min="0" :max="10000" v-model="scope.row.scale" :placeholder="t('createTable.scale')"></el-input-number>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="defaultValue" :label="t('createTable.defaultValue')" show-overflow-tooltip> <el-table-column prop="defaultValue" :label="t('createTable.defaultValue')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.defaultValue" :placeholder="t('createTable.defaultValue')"/> <el-input v-model="scope.row.defaultValue" :placeholder="t('createTable.defaultValue')" />
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column prop="primary" :label="t('createTable.primaryKey')" show-overflow-tooltip> <!-- <el-table-column prop="primary" :label="t('createTable.primaryKey')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-radio-group v-model="scope.row.primary"> <el-radio-group v-model="scope.row.primary">
<el-radio v-for="(item, index) in DIC_PROP.YES_OR_NO_NUM" :key="index" :label="item.value" > <el-radio v-for="(item, index) in DIC_PROP.YES_OR_NO_NUM" :key="index" :label="item.value" >
@@ -91,193 +90,211 @@
</el-radio-group> </el-radio-group>
</template> </template>
</el-table-column>--> </el-table-column>-->
<el-table-column prop="nullable" :label="t('createTable.nullable')" show-overflow-tooltip> <el-table-column prop="nullable" :label="t('createTable.nullable')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-radio-group v-model="scope.row.nullable"> <el-radio-group v-model="scope.row.nullable">
<el-radio v-for="(item, index) in DIC_PROP.YES_OR_NO_NUM" :key="index" :label="item.value" > <el-radio v-for="(item, index) in DIC_PROP.YES_OR_NO_NUM" :key="index" :label="item.value">
{{ getLabelByLanguage(item) }} {{ getLabelByLanguage(item) }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-row> </el-form>
</el-form> <template #footer v-if="operType !== 'view'">
<template #footer v-if="operType !== 'view'"> <span class="dialog-footer">
<span class="dialog-footer"> <el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button> <el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button> </span>
</span> </template>
</template> </el-dialog>
</el-dialog>
</template> </template>
<script setup lang="ts" name="CreateTableDialog"> <script setup lang="ts" name="CreateTableDialog">
import { useMessage, useMessageBox } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/order/create-table';
import { useI18n } from 'vue-i18n';
import { rule, validateNull } from '/@/utils/validate';
import { utils } from '/@/flow/designer/utils/common';
const emit = defineEmits(['refreshDone', 'refresh']);
import {useMessage, useMessageBox} from "/@/hooks/message"; const { t } = useI18n();
import { getObj, addObj, putObj } from '/@/api/order/create-table'
import { useI18n } from "vue-i18n"
import {rule, validateNull} from '/@/utils/validate';
import {utils} from "/@/flow/designer/utils/common";
const emit = defineEmits(['refreshDone', 'refresh']);
const { t } = useI18n(); // 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const operType = ref(false);
const title = ref('');
// 定义字典
// 定义变量内容 // 提交表单数据
const dataFormRef = ref(); const form = reactive({
const visible = ref(false); tableName: '',
const loading = ref(false); comments: '',
const operType = ref(false); databaseType: 'MySQL',
const title = ref(''); pkPolicy: 'ID_WORKER',
// 定义字典 columns: [],
});
// 提交表单数据 /**
const form = reactive({ * 校验数据源名
tableName: '', * @param {校验数据源名} rule
comments: '', * @param {*} value
databaseType: 'MySQL', * @param {*} callback
pkPolicy: 'ID_WORKER', */
columns: [], let validateDsName = (rule, value, callback) => {
}); let re = /(?=.*[a-z]|[A-Z])(?=.*_)/;
if (value && !re.test(value)) {
callback(new Error('数据源名称不合法, 组名_数据源名形式'));
} else {
callback();
}
};
/** // 定义校验规则
* 校验数据源名 const dataRules = ref({
* @param {校验数据源名} rule tableName: [
* @param {*} value { required: true, message: '表名称不能为空', trigger: 'blur' },
* @param {*} callback { max: 32, message: '长度在 32 个字符', trigger: 'blur' },
*/ { validator: validateDsName, trigger: 'blur' },
let validateDsName = (rule, value, callback) => { ],
let re = /(?=.*[a-z]|[A-Z])(?=.*_)/; comments: [{ required: true, message: '表注释不能为空', trigger: 'blur' }],
if (value && (!(re).test(value))) { databaseType: [{ required: true, message: '数据库类型不能为空', trigger: 'blur' }],
callback(new Error('数据源名称不合法, 组名_数据源名形式')) pkPolicy: [{ required: true, message: '主键策略不能为空', trigger: 'blur' }],
} else { primaryKey: [{ required: true, message: '主键字段不能为空', trigger: 'blur' }],
callback() columns: [{ required: true, message: '字段信息不能为空', trigger: 'blur' }],
} });
}
// 定义校验规则 // 打开弹窗
const dataRules = ref({ const openDialog = (type: string, id: string) => {
tableName: [ visible.value = true;
{required: true, message: '表名称不能为空', trigger: 'blur'}, operType.value = type;
{max: 32, message: '长度在 32 个字符', trigger: 'blur'}, form.id = '';
{validator: validateDsName, trigger: 'blur'}
],
comments: [{required: true, message: '表注释不能为空', trigger: 'blur'}],
databaseType: [{required: true, message: '数据库类型不能为空', trigger: 'blur'}],
pkPolicy: [{required: true, message: '主键策略不能为空', trigger: 'blur'}],
primaryKey: [{required: true, message: '主键字段不能为空', trigger: 'blur'}],
columns: [{required: true, message: '字段信息不能为空', trigger: 'blur'}],
})
// 打开弹窗 if (type === 'add') {
const openDialog = (type: string, id: string) => { title.value = t('common.addBtn');
visible.value = true } else if (type === 'edit') {
operType.value = type; title.value = t('common.editBtn');
form.id = '' } else if (type === 'view') {
title.value = t('common.viewBtn');
}
if (type === 'add') { // 重置表单数据
title.value = t('common.addBtn'); nextTick(() => {
} else if (type === 'edit') { dataFormRef.value?.resetFields();
title.value = t('common.editBtn'); if (!id) onAddItem();
} else if (type === 'view') { });
title.value = t('common.viewBtn');
}
// 重置表单数据 // 获取CreateTable信息
nextTick(() => { if (id) {
dataFormRef.value?.resetFields(); form.id = id;
if (!id) onAddItem() getCreateTableData(id);
}); }
};
// 获取CreateTable信息 const isOnSubmitRef = ref(false);
if (id) { const onCancel = async () => {
form.id = id emit('refreshDone', form.tableName, operType.value, isOnSubmitRef.value);
getCreateTableData(id) };
}
};
const isOnSubmitRef = ref(false); // 提交
const onCancel = async () => { const onSubmit = async () => {
emit('refreshDone', form.tableName, operType.value, isOnSubmitRef.value); const valid = await dataFormRef.value.validate().catch(() => {});
} if (!valid) return false;
// 提交 try {
const onSubmit = async () => { if (form.id) {
const valid = await dataFormRef.value.validate().catch(() => {}); await useMessageBox().confirm('注意若已有表中存在数据时,修改字段类型可能会报错。请确保数据值可自由转换');
if (!valid) return false; }
} catch {
return;
}
try { try {
if (form.id) { loading.value = true;
await useMessageBox().confirm("注意若已有表中存在数据时,修改字段类型可能会报错。请确保数据值可自由转换"); form.columnInfo = JSON.stringify(form.columns);
} let columns = {};
} catch { form.columns.forEach((each) => {
return; if (validateNull(each.defaultValue)) each.defaultValue = null;
} columns[each.name] = each;
});
form.columnsInfo = JSON.stringify(columns);
await addObj(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
isOnSubmitRef.value = true;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
try { // 初始化表单数据
loading.value = true; const getCreateTableData = (id: string) => {
form.columnInfo = JSON.stringify(form.columns) // 获取数据
let columns = {} loading.value = true;
form.columns.forEach(each => { getObj(id)
if (validateNull(each.defaultValue)) each.defaultValue = null .then((res: any) => {
columns[each.name] = each let columnInfo = res.data.columnInfo;
}) res.data.columns = validateNull(columnInfo) ? [] : JSON.parse(columnInfo);
form.columnsInfo = JSON.stringify(columns) Object.assign(form, res.data);
await addObj(form); })
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); .finally(() => {
visible.value = false; loading.value = false;
isOnSubmitRef.value = true; });
emit('refresh'); };
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据 const onAddItem = () => {
const getCreateTableData = (id: string) => { let find = form.columns.find((f) => f.name === 'id');
// 获取数据 if (find) {
loading.value = true let obj = { name: '', comment: '', typeName: '', precision: 0, scale: 0, defaultValue: null, nullable: 1, uniId: utils.getId() };
getObj(id).then((res: any) => { form.columns.push(obj);
let columnInfo = res.data.columnInfo; return;
res.data.columns = validateNull(columnInfo) ? [] : JSON.parse(columnInfo); }
Object.assign(form, res.data) // primary 1是0否默认-1
}).finally(() => { let id = { name: 'id', comment: '主键', typeName: 'bigint', precision: 20, scale: 0, defaultValue: null, nullable: 0, uniId: utils.getId() };
loading.value = false let create_user = {
}) name: 'create_user',
}; comment: '创建人',
typeName: 'bigint',
precision: 20,
scale: 0,
defaultValue: null,
nullable: 0,
uniId: utils.getId(),
};
let create_time = {
name: 'create_time',
comment: '创建时间',
typeName: 'date',
precision: 0,
scale: 0,
defaultValue: null,
nullable: 1,
uniId: utils.getId(),
};
form.columns.push(id);
form.columns.push(create_user);
form.columns.push(create_time);
form.primaryKey = id.name;
};
const onAddItem = () => { const handleDelete = (index: number, row: any) => {
let find = form.columns.find(f => f.name === 'id'); if (row.name === 'id' || row.name === 'create_user' || row.name === 'create_time') {
if (find) { useMessage().warning('不能删除【主键】、【创建人】、【创建时间】');
let obj = {name: '', comment: '', typeName: '', precision: 0, scale: 0, defaultValue: null, nullable: 1, uniId: utils.getId()}; return;
form.columns.push(obj); }
return form.columns.splice(index, 1);
} };
// primary 1是0否默认-1
let id = {name: 'id', comment: '主键', typeName: 'bigint', precision: 20, scale: 0, defaultValue: null, nullable: 0, uniId: utils.getId()};
let create_user = {name: 'create_user', comment: '创建人', typeName: 'bigint', precision: 20, scale: 0, defaultValue: null, nullable: 0, uniId: utils.getId()};
let create_time = {name: 'create_time', comment: '创建时间', typeName: 'date', precision: 0, scale: 0, defaultValue: null, nullable: 1, uniId: utils.getId()};
form.columns.push(id);
form.columns.push(create_user);
form.columns.push(create_time);
form.primaryKey = id.name
}
const handleDelete = (index: number, row: any) => { // 暴露变量
if (row.name === 'id' || row.name === 'create_user' || row.name === 'create_time') { defineExpose({
useMessage().warning("不能删除【主键】、【创建人】、【创建时间】"); openDialog,
return });
}
form.columns.splice(index, 1)
}
// 暴露变量
defineExpose({
openDialog
});
</script> </script>

View File

@@ -1,34 +1,34 @@
export default { export default {
createTable: { createTable: {
index: '#', index: '#',
importcreateTableTip: 'import CreateTable', importcreateTableTip: 'import CreateTable',
id: 'id', id: 'id',
tableName: 'tableName', tableName: 'tableName',
comments: 'comments', comments: 'comments',
comment: 'comment', comment: 'comment',
databaseType: 'databaseType', databaseType: 'databaseType',
pkPolicy: 'pkPolicy', pkPolicy: 'pkPolicy',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
columnInfo: 'columnInfo', columnInfo: 'columnInfo',
primaryKey: 'primaryKey', primaryKey: 'primaryKey',
inputIdTip: 'input id', inputIdTip: 'input id',
inputTableNameTip: 'input tableName', inputTableNameTip: 'input tableName',
inputCommentsTip: 'input comments', inputCommentsTip: 'input comments',
inputCommentTip: 'input comment', inputCommentTip: 'input comment',
inputDatabaseTypeTip: 'input databaseType', inputDatabaseTypeTip: 'input databaseType',
inputPkPolicyTip: 'input pkPolicy', inputPkPolicyTip: 'input pkPolicy',
inputPrimaryKeyTip: 'input primaryKey', inputPrimaryKeyTip: 'input primaryKey',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputColumnInfoTip: 'input columnInfo', inputColumnInfoTip: 'input columnInfo',
name: 'name', name: 'name',
typeName: 'typeName', typeName: 'typeName',
precision: 'precision', precision: 'precision',
scale: 'scale', scale: 'scale',
defaultValue: 'defaultValue', defaultValue: 'defaultValue',
nullable: 'nullable', nullable: 'nullable',
} },
} };

View File

@@ -1,34 +1,34 @@
export default { export default {
createTable: { createTable: {
index: '#', index: '#',
importcreateTableTip: '导入自动创建表管理', importcreateTableTip: '导入自动创建表管理',
id: '主键ID', id: '主键ID',
tableName: '表名称', tableName: '表名称',
comments: '表注释', comments: '表注释',
comment: '字段注释', comment: '字段注释',
databaseType: '数据库类型', databaseType: '数据库类型',
pkPolicy: '主键策略', pkPolicy: '主键策略',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
columnInfo: '字段信息', columnInfo: '字段信息',
primaryKey: '主键字段', primaryKey: '主键字段',
inputIdTip: '请输入主键ID', inputIdTip: '请输入主键ID',
inputTableNameTip: '请输入表名称', inputTableNameTip: '请输入表名称',
inputCommentsTip: '请输入表注释', inputCommentsTip: '请输入表注释',
inputCommentTip: '请输入字段注释', inputCommentTip: '请输入字段注释',
inputDatabaseTypeTip: '请输入数据库引擎', inputDatabaseTypeTip: '请输入数据库引擎',
inputPkPolicyTip: '请输入主键策略', inputPkPolicyTip: '请输入主键策略',
inputPrimaryKeyTip: '请选择主键字段', inputPrimaryKeyTip: '请选择主键字段',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputColumnInfoTip: '请输入字段信息', inputColumnInfoTip: '请输入字段信息',
name: '字段名称', name: '字段名称',
typeName: '字段类型', typeName: '字段类型',
precision: '字段长度', precision: '字段长度',
scale: '小数位数', scale: '小数位数',
defaultValue: '默认值', defaultValue: '默认值',
nullable: '允许NULL', nullable: '允许NULL',
} },
} };

View File

@@ -1,170 +1,174 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('createTable.tableName')" prop="tableName" > <el-form-item :label="$t('createTable.tableName')" prop="tableName">
<el-input :placeholder="t('createTable.inputTableNameTip')" v-model="state.queryForm.tableName" <el-input :placeholder="t('createTable.inputTableNameTip')" v-model="state.queryForm.tableName" style="max-width: 180px" />
style="max-width: 180px" /> </el-form-item>
</el-form-item> <el-form-item>
<el-form-item> <el-button icon="search" type="primary" @click="getDataList">
<el-button icon="search" type="primary" @click="getDataList"> {{ $t('common.queryBtn') }}
{{ $t('common.queryBtn') }} </el-button>
</el-button> <el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button> </el-form-item>
</el-form-item> </el-form>
</el-form> </el-row>
</el-row> <el-row>
<el-row> <div class="mb8" style="width: 100%">
<div class="mb8" style="width: 100%"> <el-tooltip placement="top">
<el-tooltip placement="top"> <template #content>
<template #content> {{ $t('common.addBtn') }}
{{ $t('common.addBtn') }} </template>
</template> <el-button icon="Plus" type="primary" class="ml10" @click="formDialogRef.openDialog('add')" v-auth="'order_createtable_add'"> </el-button>
<el-button icon="Plus" type="primary" class="ml10" @click="formDialogRef.openDialog('add')" </el-tooltip>
v-auth="'order_createtable_add'">
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top">
<template #content> <template #content>
{{ $t('common.delBtn') }} {{ $t('common.delBtn') }}
</template> </template>
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" <el-button
v-auth="'order_createtable_del'" @click="handleDelete(selectObjs)"> plain
</el-button> :disabled="multiple"
</el-tooltip> icon="Delete"
type="primary"
class="ml10"
v-auth="'order_createtable_del'"
@click="handleDelete(selectObjs)"
>
</el-button>
</el-tooltip>
<right-toolbar v-model:showSearch="showSearch" :export="'order_createtable_export'" <right-toolbar
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" v-model:showSearch="showSearch"
@queryTable="getDataList"></right-toolbar> :export="'order_createtable_export'"
</div> @exportExcel="exportExcel"
</el-row> class="ml10"
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" style="float: right; margin-right: 20px"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> @queryTable="getDataList"
<el-table-column type="selection" width="40" align="center" /> ></right-toolbar>
<el-table-column type="index" :label="t('createTable.index')" width="40" /> </div>
<el-table-column prop="tableName" :label="t('createTable.tableName')" show-overflow-tooltip/> </el-row>
<el-table-column prop="comments" :label="t('createTable.comments')" show-overflow-tooltip/> <el-table
<el-table-column prop="databaseType" :label="t('createTable.databaseType')" show-overflow-tooltip> :data="state.dataList"
<template #default="scope"> v-loading="state.loading"
<dict-tag :options="DIC_PROP.DATABASE_TYPE" :value="scope.row.databaseType"></dict-tag> style="width: 100%"
</template> @selection-change="handleSelectionChange"
</el-table-column> @sort-change="sortChangeHandle"
<el-table-column prop="pkPolicy" :label="t('createTable.pkPolicy')" show-overflow-tooltip/> >
<el-table-column prop="createUser" :label="t('createTable.createUser')" show-overflow-tooltip> <el-table-column type="selection" width="40" align="center" />
<template #default="scope"> <el-table-column type="index" :label="t('createTable.index')" width="40" />
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" <el-table-column prop="tableName" :label="t('createTable.tableName')" show-overflow-tooltip />
:valueKey="'userId'" :showKey="'name'"></convert-name> <el-table-column prop="comments" :label="t('createTable.comments')" show-overflow-tooltip />
</template> <el-table-column prop="databaseType" :label="t('createTable.databaseType')" show-overflow-tooltip>
</el-table-column> <template #default="scope">
<el-table-column prop="createTime" :label="t('createTable.createTime')" show-overflow-tooltip/> <dict-tag :options="DIC_PROP.DATABASE_TYPE" :value="scope.row.databaseType"></dict-tag>
<el-table-column :label="$t('common.action')" width="100"> </template>
<template #default="scope"> </el-table-column>
<el-tooltip placement="top"> <el-table-column prop="pkPolicy" :label="t('createTable.pkPolicy')" show-overflow-tooltip />
<template #content> <el-table-column prop="createUser" :label="t('createTable.createUser')" show-overflow-tooltip>
{{ $t('common.viewBtn') }} <template #default="scope">
</template> <convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)"> </template>
</el-button> </el-table-column>
</el-tooltip> <el-table-column prop="createTime" :label="t('createTable.createTime')" show-overflow-tooltip />
<el-tooltip placement="top"> <el-table-column :label="$t('common.action')" width="100">
<template #content> <template #default="scope">
{{ $t('common.editBtn') }} <el-tooltip placement="top">
</template> <template #content>
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)"> {{ $t('common.viewBtn') }}
</el-button> </template>
</el-tooltip> <el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)"> </el-button>
<el-tooltip placement="top"> </el-tooltip>
<template #content> <el-tooltip placement="top">
{{ $t('common.delBtn') }} <template #content>
</template> {{ $t('common.editBtn') }}
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </template>
</el-button> <el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)"> </el-button>
</el-tooltip> </el-tooltip>
</template> <el-tooltip placement="top">
</el-table-column> <template #content>
</el-table> {{ $t('common.delBtn') }}
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" /> </template>
</div> <el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" /> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div> </div>
</template> </template>
<script setup lang="ts" name="systemCreateTable"> <script setup lang="ts" name="systemCreateTable">
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from "/@/api/order/create-table"; import { fetchList, delObjs } from '/@/api/order/create-table';
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from "vue-i18n"; import { useI18n } from 'vue-i18n';
import { onLoaded } from "/@/flow/components/convert-name/convert"; import { onLoaded } from '/@/flow/components/convert-name/convert';
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { t } = useI18n() const { t } = useI18n();
// 定义查询字典 // 定义查询字典
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: fetchList, pageList: fetchList,
onLoaded: onLoaded({key: "createUser"}), onLoaded: onLoaded({ key: 'createUser' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/create-table/export', state.queryForm, 'create-table.xlsx') downBlobFile('/order/create-table/export', state.queryForm, 'create-table.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await delObjs(ids); await delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
</script> </script>

View File

@@ -1,275 +1,278 @@
<template> <template>
<div> <div>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
:disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20" v-if="!hiddenFields.type">
<el-col :span="12" class="mb20" v-if="!hiddenFields.type"> <el-form-item :label="t('askLeave.type')" prop="type">
<el-form-item :label="t('askLeave.type')" prop="type"> <el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type" />
<el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type"/> </el-form-item>
</el-form-item> </el-col>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.startTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.startTime">
<el-form-item :label="t('askLeave.startTime')" prop="startTime"> <el-form-item :label="t('askLeave.startTime')" prop="startTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputStartTimeTip')" :disabled="disabledFields.startTime" <el-date-picker
v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputStartTimeTip')"
</el-col> :disabled="disabledFields.startTime"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.endTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.endTime">
<el-form-item :label="t('askLeave.endTime')" prop="endTime"> <el-form-item :label="t('askLeave.endTime')" prop="endTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputEndTimeTip')" :disabled="disabledFields.endTime" <el-date-picker
v-model="form.endTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputEndTimeTip')"
</el-col> :disabled="disabledFields.endTime"
v-model="form.endTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.days"> <el-col :span="12" class="mb20" v-if="!hiddenFields.days">
<el-form-item :label="t('askLeave.days')" prop="days"> <el-form-item :label="t('askLeave.days')" prop="days">
<el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days"/> <el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson"> <el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson">
<el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson"> <el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" :disabled="disabledFields.carbonCopyPerson" <el-select
remote :remote-method="remoteMethod" :reserve-keyword="false" v-model="form.carbonCopyPerson"
clearable filterable multiple> :placeholder="t('askLeave.inputCarbonCopyPersonTip')"
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :disabled="disabledFields.carbonCopyPerson"
:value="item.userId"> remote
:remote-method="remoteMethod"
:reserve-keyword="false"
clearable
filterable
multiple
>
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"> </el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
</el-option> <el-col :span="12" class="mb20" v-if="!hiddenFields.remark">
</el-select> <el-form-item :label="t('askLeave.remark')" prop="remark">
</el-tooltip> <el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.remark"> <el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls">
<el-form-item :label="t('askLeave.remark')" prop="remark"> <el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls">
<el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark"/> <el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-form>
<template v-if="data.submitBtn">
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{ t('jfI18n.print') }} </el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }}</el-button>
</span>
</footer>
</template>
<template v-else>
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{ t('jfI18n.print') }} </el-button>
</span>
</footer>
</template>
<el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls"> <!-- 打印表单 -->
<el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls"> <el-dialog v-model="data.showTinymceView" top="20px" width="700px" :title="data.tinymceTitle" append-to-body @close="closePrint">
<el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls"/> <tinymce-view v-if="data.showTinymceView" :currFlowForm="data.currFlowForm" :elTab="data.elTab"></tinymce-view>
</el-form-item> </el-dialog>
</el-col> </div>
</el-row>
</el-form>
<template v-if="data.submitBtn">
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{
t('jfI18n.print')
}}
</el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }}</el-button>
</span>
</footer>
</template>
<template v-else>
<footer class="el-dialog__footer">
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{
t('jfI18n.print')
}}
</el-button>
</span>
</footer>
</template>
<!-- 打印表单 -->
<el-dialog v-model="data.showTinymceView" top="20px" width="700px"
:title="data.tinymceTitle" append-to-body
@close="closePrint">
<tinymce-view v-if="data.showTinymceView" :currFlowForm="data.currFlowForm" :elTab="data.elTab"></tinymce-view>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="CustomLeaveFlow"> <script setup lang="ts" name="CustomLeaveFlow">
import { useMessage } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import { rule, validateNull } from '/@/utils/validate';
import { onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert.ts';
import * as orderVue from '/@/api/order/order-key-vue';
import * as runApplication from '/@/api/order/run-application';
import { parseWithFunctions } from '/@/flow';
import { deepClone } from '/@/utils/other';
import { handleCustomFormPerm, handleFormPrint } from '/@/flow/utils/form-perm';
import { initCustomFormMethods } from '../index';
import {useMessage} from "/@/hooks/message"; const { t } = useI18n();
import {useI18n} from "vue-i18n" const emits = defineEmits(['handleJob']);
import {rule, validateNull} from '/@/utils/validate'; // 引入组件
import {onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey} from "/@/flow/components/convert-name/convert.ts"; const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue'));
import * as orderVue from "/@/api/order/order-key-vue";
import * as runApplication from "/@/api/order/run-application";
import {parseWithFunctions} from "/@/flow";
import {deepClone} from "/@/utils/other";
import {handleCustomFormPerm, handleFormPrint} from "/@/flow/utils/form-perm";
import {initCustomFormMethods} from "../index";
const {t} = useI18n(); // 定义变量内容
const emits = defineEmits(["handleJob"]); const dataFormRef = ref();
// 引入组件 const loading = ref(false);
const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue')); const operType = ref(false);
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'carbonCopyPerson' });
onMounted(async () => {
// await onLoad(dicData);
});
// 定义变量内容 function remoteMethod(query: string) {
const dataFormRef = ref(); remoteMethodByKey(query, onLoad, dicData, 'userName', 'carbonCopyPerson');
const loading = ref(false); }
const operType = ref(false);
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "carbonCopyPerson"});
onMounted(async () => {
// await onLoad(dicData);
});
function remoteMethod(query: string) { // 提交表单数据
remoteMethodByKey(query, onLoad, dicData, 'userName', "carbonCopyPerson") const form = reactive({
} type: '',
startTime: '',
endTime: '',
days: '',
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
type: '', startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
startTime: '', days: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
endTime: '', remark: [{ required: true, message: '请假事由不能为空', trigger: 'blur' }],
days: '', });
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 定义校验规则 const props = defineProps({
const dataRules = ref({ currJob: {
startTime: [{required: true, message: '开始时间不能为空', trigger: 'blur'}], type: Object,
days: [{required: true, message: '请假天数不能为空', trigger: 'blur'}], default: null,
remark: [{required: true, message: '请假事由不能为空', trigger: 'blur'}], },
}) currElTab: {
type: Object,
default: {},
},
});
const props = defineProps({ const data = reactive({
currJob: { currFlowForm: {},
type: Object, formData: {},
default: null, showTinymceView: false,
}, tinymceTitle: null,
currElTab: { submitBtn: true,
type: Object, elTab: null,
default: {}, });
},
});
const data = reactive({ function initJobData() {
currFlowForm: {}, handleGetObj(props.currJob.orderId);
formData: {}, }
showTinymceView: false,
tinymceTitle: null,
submitBtn: true,
elTab: null
})
function initJobData() { const fieldsPerm = {
handleGetObj(props.currJob.orderId) type: false,
} startTime: false,
endTime: false,
days: false,
carbonCopyPerson: false,
remark: false,
imgUrls: false,
};
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
const fieldsPerm = { function handleGetObj(id) {
type: false, runApplication.getObj(id).then(async (resp) => {
startTime: false, let res = resp.data ? resp.data : {};
endTime: false, data.currFlowForm = res;
days: false, let data2 = parseWithFunctions(res.formData);
carbonCopyPerson: false, Object.assign(form, data2);
remark: false, await onFormLoaded(dicData, form);
imgUrls: false, data.currFlowForm.runJobId = props.currJob.id;
} data.formData = data2;
// 定义字段显隐 await initFormPermPrint();
const hiddenFields = reactive(fieldsPerm); });
// 定义字段是否可编辑 }
const disabledFields = reactive(deepClone(fieldsPerm));
function handleGetObj(id) { const methods = initCustomFormMethods(data, disabledFields, operType);
runApplication.getObj(id).then(async resp => {
let res = resp.data ? resp.data : {}
data.currFlowForm = res
let data2 = parseWithFunctions(res.formData)
Object.assign(form, data2)
await onFormLoaded(dicData, form);
data.currFlowForm.runJobId = props.currJob.id
data.formData = data2
await initFormPermPrint()
})
}
const methods = initCustomFormMethods(data, disabledFields, operType) async function initFormPermPrint() {
let elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id);
// 处理表单权限
let res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab);
// 判断是否仅查看
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback);
// 采用elTab配置的模板
await handleFormPrint(data.currFlowForm, elTab.type, elTab.id, '1');
data.elTab = elTab;
}
async function initFormPermPrint() { function printForm() {
let elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id) closePrint(true, false);
// 处理表单权限 data.tinymceTitle = data.currFlowForm.formName;
let res = await handleCustomFormPerm(props, hiddenFields, disabledFields, elTab) data.showTinymceView = true;
// 判断是否仅查看 }
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback)
// 采用elTab配置的模板
await handleFormPrint(data.currFlowForm, elTab.type, elTab.id, '1')
data.elTab = elTab
}
function printForm() { function closePrint(isInit, isSave) {
closePrint(true, false) if (isInit) {
data.tinymceTitle = data.currFlowForm.formName data.currFlowForm.formData = form;
data.showTinymceView = true data.currFlowForm.dicData = { carbonCopyPerson: dicData.carbonCopyPerson };
} data.currFlowForm['carbonCopyPerson.valueKey'] = 'userId';
data.currFlowForm['carbonCopyPerson.showKey'] = 'name';
} else {
delete data.currFlowForm.dicData;
delete data.currFlowForm['carbonCopyPerson.valueKey'];
delete data.currFlowForm['carbonCopyPerson.showKey'];
}
if (isSave) delete data.currFlowForm.printInfo;
}
function closePrint(isInit, isSave){ async function submitForm() {
if (isInit) { try {
data.currFlowForm.formData = form loading.value = true;
data.currFlowForm.dicData = {carbonCopyPerson: dicData.carbonCopyPerson} let formJson = saveInitData(form);
data.currFlowForm['carbonCopyPerson.valueKey'] = 'userId' await runApplication.putObj(formJson);
data.currFlowForm['carbonCopyPerson.showKey'] = 'name' orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits);
} else { useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText'));
delete data.currFlowForm.dicData } catch (err: any) {
delete data.currFlowForm['carbonCopyPerson.valueKey'] useMessage().error(err.msg);
delete data.currFlowForm['carbonCopyPerson.showKey'] } finally {
} loading.value = false;
if (isSave) delete data.currFlowForm.printInfo }
} }
async function submitForm() { function saveInitData(form) {
try { closePrint(false, true);
loading.value = true; data.currFlowForm.formData = validateNull(form) ? null : form;
let formJson = saveInitData(form); let formJson = deepClone(data.currFlowForm);
await runApplication.putObj(formJson) if (!validateNull(form)) {
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits) formJson.formData = Object.assign({}, data.formData, formJson.formData);
useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText')); }
} catch (err: any) { formJson.formData = JSON.stringify(formJson.formData);
useMessage().error(err.msg); return formJson;
} finally { }
loading.value = false;
}
}
function saveInitData(form) { // 监听双向绑定
closePrint(false, true) watch(
data.currFlowForm.formData = validateNull(form) ? null : form () => props.currJob.id,
let formJson = deepClone(data.currFlowForm) () => {
if (!validateNull(form)) { initJobData();
formJson.formData = Object.assign({}, data.formData, formJson.formData); }
} );
formJson.formData = JSON.stringify(formJson.formData)
return formJson;
}
// 监听双向绑定 onMounted(() => {
watch( initJobData();
() => props.currJob.id, });
() => {
initJobData();
}
);
onMounted(() => {
initJobData()
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,242 +1,257 @@
<template> <template>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'"> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="12" class="mb20" v-if="!hiddenFields.type"> <el-col :span="12" class="mb20" v-if="!hiddenFields.type">
<el-form-item :label="t('askLeave.type')" prop="type"> <el-form-item :label="t('askLeave.type')" prop="type">
<el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type"/> <el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" :disabled="disabledFields.type" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.startTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.startTime">
<el-form-item :label="t('askLeave.startTime')" prop="startTime"> <el-form-item :label="t('askLeave.startTime')" prop="startTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputStartTimeTip')" :disabled="disabledFields.startTime" <el-date-picker
v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputStartTimeTip')"
</el-col> :disabled="disabledFields.startTime"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.endTime"> <el-col :span="12" class="mb20" v-if="!hiddenFields.endTime">
<el-form-item :label="t('askLeave.endTime')" prop="endTime"> <el-form-item :label="t('askLeave.endTime')" prop="endTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputEndTimeTip')" :disabled="disabledFields.endTime" <el-date-picker
v-model="form.endTime" :value-format="dateTimeStr"></el-date-picker> type="datetime"
</el-form-item> :placeholder="t('askLeave.inputEndTimeTip')"
</el-col> :disabled="disabledFields.endTime"
v-model="form.endTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.days"> <el-col :span="12" class="mb20" v-if="!hiddenFields.days">
<el-form-item :label="t('askLeave.days')" prop="days"> <el-form-item :label="t('askLeave.days')" prop="days">
<el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days"/> <el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" :disabled="disabledFields.days" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson"> <el-col :span="12" class="mb20" v-if="!hiddenFields.carbonCopyPerson">
<el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson"> <el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" clearable filterable multiple <el-select
remote :remote-method="remoteMethod" :reserve-keyword="false" v-model="form.carbonCopyPerson"
:disabled="disabledFields.carbonCopyPerson"> :placeholder="t('askLeave.inputCarbonCopyPersonTip')"
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option> clearable
</el-select> filterable
</el-tooltip> multiple
</el-form-item> remote
</el-col> :remote-method="remoteMethod"
:reserve-keyword="false"
:disabled="disabledFields.carbonCopyPerson"
>
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.remark"> <el-col :span="12" class="mb20" v-if="!hiddenFields.remark">
<el-form-item :label="t('askLeave.remark')" prop="remark"> <el-form-item :label="t('askLeave.remark')" prop="remark">
<el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark"/> <el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" :disabled="disabledFields.remark" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls"> <el-col :span="12" class="mb20" v-if="!hiddenFields.imgUrls">
<el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls"> <el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls">
<el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls"/> <el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" :disabled="disabledFields.imgUrls" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-row> </el-form>
</el-form>
</template> </template>
<script setup lang="ts" name="CustomLeaveForm"> <script setup lang="ts" name="CustomLeaveForm">
import { useMessage } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import { rule, validateNull } from '/@/utils/validate';
import { onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert.ts';
import * as common from '/@/flow/support/common';
import { paramsFilter, parseWithFunctions } from '/@/flow';
import { deepClone } from '/@/utils/other';
import * as runApplication from '/@/api/order/run-application';
import { handleFormPrint, handleFormStartPerm } from '/@/flow/utils/form-perm';
import { DIC_PROP } from '/@/flow/support/dict-prop';
import { initCustomFormMethods } from '../index';
import { currFormIsView } from '/@/api/order/order-key-vue';
import { useMessage } from "/@/hooks/message"; const { t } = useI18n();
import { useI18n } from "vue-i18n"
import {rule, validateNull} from '/@/utils/validate';
import {onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey} from "/@/flow/components/convert-name/convert.ts";
import * as common from '/@/flow/support/common'
import {paramsFilter, parseWithFunctions} from "/@/flow";
import {deepClone} from "/@/utils/other";
import * as runApplication from "/@/api/order/run-application";
import {handleFormPrint, handleFormStartPerm} from "/@/flow/utils/form-perm";
import {DIC_PROP} from "/@/flow/support/dict-prop";
import {initCustomFormMethods} from "../index";
import {currFormIsView} from "/@/api/order/order-key-vue";
const { t } = useI18n(); const props = defineProps({
currJob: {
type: Object,
default: null,
},
});
const props = defineProps({ // 定义变量内容
currJob: { const dataFormRef = ref();
type: Object, const loading = ref(false);
default: null, const operType = ref(false);
} // 定义字典
}); const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'carbonCopyPerson' });
onMounted(async () => {
// await onLoad(dicData);
openForm(props.currJob.operType, props.currJob.id);
props.currJob.onSubmit = onSubmit;
props.currJob.onTemp = onTemp;
props.currJob.getFormData = getFormData;
});
// 定义变量内容 function remoteMethod(query: string) {
const dataFormRef = ref(); remoteMethodByKey(query, onLoad, dicData, 'userName', 'carbonCopyPerson');
const loading = ref(false); }
const operType = ref(false);
// 定义字典
const dicData = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "carbonCopyPerson"});
onMounted(async () => {
// await onLoad(dicData);
openForm(props.currJob.operType, props.currJob.id)
props.currJob.onSubmit = onSubmit
props.currJob.onTemp = onTemp
props.currJob.getFormData = getFormData
});
function remoteMethod(query: string) { // 提交表单数据
remoteMethodByKey(query, onLoad, dicData, 'userName', "carbonCopyPerson") const form = reactive({
} type: '',
startTime: '',
endTime: '',
days: '',
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
type: '', startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
startTime: '', days: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
endTime: '', remark: [{ required: true, message: '请假事由不能为空', trigger: 'blur' }],
days: '', });
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 定义校验规则 // 打开表单
const dataRules = ref({ const openForm = (type: string, id: string) => {
startTime: [{required: true, message: '开始时间不能为空', trigger: 'blur'}], operType.value = type;
days: [{required: true, message: '请假天数不能为空', trigger: 'blur'}],
remark: [{required: true, message: '请假事由不能为空', trigger: 'blur'}],
})
// 打开表单 // 重置表单数据
const openForm = (type: string, id: string) => { nextTick(() => {
operType.value = type; dataFormRef.value?.resetFields();
// 获取表单信息
if (id) {
rowEditInitData(type, id);
}
});
};
// 重置表单数据 const fieldsPerm = {
nextTick(() => { type: false,
dataFormRef.value?.resetFields(); startTime: false,
// 获取表单信息 endTime: false,
if (id) { days: false,
rowEditInitData(type, id) carbonCopyPerson: false,
} remark: false,
}); imgUrls: false,
}; };
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
const fieldsPerm = { const methods = initCustomFormMethods({}, disabledFields);
type: false,
startTime: false,
endTime: false,
days: false,
carbonCopyPerson: false,
remark: false,
imgUrls: false,
}
// 定义字段显隐
const hiddenFields = reactive(fieldsPerm);
// 定义字段是否可编辑
const disabledFields = reactive(deepClone(fieldsPerm));
const methods = initCustomFormMethods({}, disabledFields) async function rowEditInitData(type, id) {
// 处理表单权限 开始节点必须配置表单
let res = await handleFormStartPerm(hiddenFields, disabledFields, null, props.currJob.defFlowId, null, null);
await currFormIsView(methods, res.elTab, true, res.callback);
if (type === 'add') {
props.currJob.formId = id;
return;
}
if (validateNull(props.currJob.formData)) return;
let data = parseWithFunctions(props.currJob.formData);
Object.assign(form, data);
await onFormLoaded(dicData, form);
await initFormPermPrint();
}
async function rowEditInitData(type, id) { async function initFormPermPrint() {
// 处理表单权限 开始节点必须配置表单 // 处理表单权限
let res = await handleFormStartPerm(hiddenFields, disabledFields, null, props.currJob.defFlowId, null, null) await handleFormPrint(props.currJob, props.currJob.type, props.currJob.formId, '1');
await currFormIsView(methods, res.elTab, true, res.callback) props.currJob.resolvePrintForm = printForm;
if (type === 'add') { props.currJob.resolveClosePrint = closePrint;
props.currJob.formId = id }
return
}
if (validateNull(props.currJob.formData)) return
let data = parseWithFunctions(props.currJob.formData)
Object.assign(form, data)
await onFormLoaded(dicData, form);
await initFormPermPrint()
}
async function initFormPermPrint() { function printForm() {
// 处理表单权限 closePrint(true, false);
await handleFormPrint(props.currJob, props.currJob.type, props.currJob.formId, '1') }
props.currJob.resolvePrintForm = printForm
props.currJob.resolveClosePrint = closePrint
}
function printForm() { function closePrint(isInit, isSave) {
closePrint(true, false) if (isInit) {
} props.currJob.formData = form;
props.currJob.dicData = { carbonCopyPerson: dicData.carbonCopyPerson };
props.currJob['carbonCopyPerson.valueKey'] = 'userId';
props.currJob['carbonCopyPerson.showKey'] = 'name';
} else {
delete props.currJob.dicData;
delete props.currJob['carbonCopyPerson.valueKey'];
delete props.currJob['carbonCopyPerson.showKey'];
}
if (isSave) delete props.currJob.printInfo;
}
function closePrint(isInit, isSave){ // 暂存
if (isInit) { const onTemp = async () => {
props.currJob.formData = form let clone = { operType: operType.value, form: form };
props.currJob.dicData = {carbonCopyPerson: dicData.carbonCopyPerson} common.handleCloneSubmit(clone);
props.currJob['carbonCopyPerson.valueKey'] = 'userId' try {
props.currJob['carbonCopyPerson.showKey'] = 'name' loading.value = true;
} else { let formJson = saveInitData(form);
delete props.currJob.dicData await runApplication.tempStore(formJson);
delete props.currJob['carbonCopyPerson.valueKey'] useMessage().success('暂存成功,请在【我的申请】查看');
delete props.currJob['carbonCopyPerson.showKey'] return true;
} } catch (err: any) {
if (isSave) delete props.currJob.printInfo useMessage().error(err.msg);
} } finally {
loading.value = false;
}
};
// 暂存 // 提交
const onTemp = async () => { const onSubmit = async () => {
let clone = {operType: operType.value, form: form}; const valid = await dataFormRef.value.validate().catch(() => {});
common.handleCloneSubmit(clone); if (!valid) return false;
try {
loading.value = true;
let formJson = saveInitData(form);
await runApplication.tempStore(formJson)
useMessage().success("暂存成功,请在【我的申请】查看");
return true;
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
}
// 提交 let clone = { operType: operType.value, form: form };
const onSubmit = async () => { common.handleCloneSubmit(clone);
const valid = await dataFormRef.value.validate().catch(() => {}); try {
if (!valid) return false; loading.value = true;
let formJson = saveInitData(form);
props.currJob.status !== DIC_PROP.ORDER_STATUS[0].value ? await runApplication.addObj(formJson) : await runApplication.putObj(formJson);
useMessage().success(t(formJson.id ? 'common.editSuccessText' : '发起成功,请在【我的申请】查看'));
return true;
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
let clone = {operType: operType.value, form: form}; function saveInitData(form) {
common.handleCloneSubmit(clone) closePrint(false, true);
try { form = paramsFilter(form);
loading.value = true; props.currJob.formData = validateNull(form) ? undefined : form;
let formJson = saveInitData(form); let formJson = deepClone(props.currJob);
props.currJob.status !== DIC_PROP.ORDER_STATUS[0].value ? await runApplication.addObj(formJson) : await runApplication.putObj(formJson); let clone = { operType: props.currJob.operType, form: formJson };
useMessage().success(t(formJson.id ? 'common.editSuccessText' : '发起成功,请在【我的申请】查看')); common.handleCloneSubmit(clone);
return true; formJson.formData = JSON.stringify(formJson.formData);
} catch (err: any) { return formJson;
useMessage().error(err.msg); }
} finally {
loading.value = false;
}
};
function saveInitData(form) {
closePrint(false, true)
form = paramsFilter(form)
props.currJob.formData = validateNull(form) ? undefined : form
let formJson = deepClone(props.currJob)
let clone = {operType: props.currJob.operType, form: formJson};
common.handleCloneSubmit(clone)
formJson.formData = JSON.stringify(formJson.formData)
return formJson;
}
async function getFormData() {
return saveInitData(form);
}
async function getFormData() {
return saveInitData(form);
}
</script> </script>

View File

@@ -1,168 +1,177 @@
<template> <template>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'"> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.type')" prop="type"> <el-form-item :label="t('askLeave.type')" prop="type">
<el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')"/> <el-input v-model="form.type" :placeholder="t('askLeave.inputTypeTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.startTime')" prop="startTime"> <el-form-item :label="t('askLeave.startTime')" prop="startTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputStartTimeTip')" v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> <el-date-picker
</el-form-item> type="datetime"
</el-col> :placeholder="t('askLeave.inputStartTimeTip')"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.endTime')" prop="endTime"> <el-form-item :label="t('askLeave.endTime')" prop="endTime">
<el-date-picker type="datetime" :placeholder="t('askLeave.inputEndTimeTip')" v-model="form.endTime" :value-format="dateTimeStr"></el-date-picker> <el-date-picker
</el-form-item> type="datetime"
</el-col> :placeholder="t('askLeave.inputEndTimeTip')"
v-model="form.endTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.days')" prop="days"> <el-form-item :label="t('askLeave.days')" prop="days">
<el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')"/> <el-input v-model="form.days" :placeholder="t('askLeave.inputDaysTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson"> <el-form-item :label="t('askLeave.carbonCopyPerson')" prop="carbonCopyPerson">
<el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" clearable filterable multiple> <el-select v-model="form.carbonCopyPerson" :placeholder="t('askLeave.inputCarbonCopyPersonTip')" clearable filterable multiple>
<el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option> <el-option v-for="(item, index) in dicData.carbonCopyPerson" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.remark')" prop="remark"> <el-form-item :label="t('askLeave.remark')" prop="remark">
<el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')"/> <el-input v-model="form.remark" type="textarea" :placeholder="t('askLeave.inputRemarkTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls"> <el-form-item :label="t('askLeave.imgUrls')" prop="imgUrls">
<el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')"/> <el-input v-model="form.imgUrls" :placeholder="t('askLeave.inputImgUrlsTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-row> </el-form>
</el-form>
</template> </template>
<script setup lang="ts" name="CustomAskLeave"> <script setup lang="ts" name="CustomAskLeave">
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, tempStore } from '/@/api/order/ask-leave';
import { useI18n } from 'vue-i18n';
import { rule } from '/@/utils/validate';
import { onLoadDicUrl } from '/@/flow/components/convert-name/convert.ts';
import * as common from '/@/flow/support/common';
import { DIC_PROP } from '/@/flow/support/dict-prop';
import { useMessage } from "/@/hooks/message"; const { t } = useI18n();
import {getObj, addObj, putObj, tempStore} from '/@/api/order/ask-leave'
import { useI18n } from "vue-i18n"
import { rule } from '/@/utils/validate';
import {onLoadDicUrl} from "/@/flow/components/convert-name/convert.ts";
import * as common from '/@/flow/support/common'
import {DIC_PROP} from "/@/flow/support/dict-prop";
const { t } = useI18n(); const props = defineProps({
currJob: {
type: Object,
default: null,
},
});
const props = defineProps({ // 定义变量内容
currJob: { const dataFormRef = ref();
type: Object, const loading = ref(false);
default: null, const operType = ref(false);
} // 定义字典
}); const dicData = reactive({});
const onLoad = onLoadDicUrl({ key: 'carbonCopyPerson' });
onMounted(() => {
onLoad(dicData);
openForm(props.currJob.operType, props.currJob.id);
props.currJob.onSubmit = onSubmit;
props.currJob.onTemp = onTemp;
});
// 定义变量内容 // 提交表单数据
const dataFormRef = ref(); const form = reactive({
const loading = ref(false); type: '',
const operType = ref(false); startTime: '',
// 定义字典 endTime: '',
const dicData = reactive({}); days: '',
const onLoad = onLoadDicUrl({key: "carbonCopyPerson"}); carbonCopyPerson: [],
onMounted(() => { remark: '',
onLoad(dicData); imgUrls: '',
openForm(props.currJob.operType, props.currJob.id) });
props.currJob.onSubmit = onSubmit
props.currJob.onTemp = onTemp
});
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
type: '', startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
startTime: '', days: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }],
endTime: '', remark: [{ required: true, message: '请假事由不能为空', trigger: 'blur' }],
days: '', });
carbonCopyPerson: [],
remark: '',
imgUrls: '',
});
// 定义校验规则 // 打开表单
const dataRules = ref({ const openForm = (type: string, id: string) => {
startTime: [{required: true, message: '开始时间不能为空', trigger: 'blur'}], operType.value = type;
days: [{required: true, message: '请假天数不能为空', trigger: 'blur'}], form.id = '';
remark: [{required: true, message: '请假事由不能为空', trigger: 'blur'}],
})
// 打开表单 // 重置表单数据
const openForm = (type: string, id: string) => { nextTick(() => {
operType.value = type; dataFormRef.value?.resetFields();
form.id = '' });
// 重置表单数据 // 获取AskLeave信息
nextTick(() => { if (id) {
dataFormRef.value?.resetFields(); form.id = id;
}); getAskLeaveData(id);
}
};
// 获取AskLeave信息 // 暂存
if (id) { const onTemp = async () => {
form.id = id let clone = { operType: operType.value, form: form };
getAskLeaveData(id) common.handleCloneSubmit(clone);
} try {
}; loading.value = true;
await tempStore(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
return true;
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暂存 // 提交
const onTemp = async () => { const onSubmit = async () => {
let clone = {operType: operType.value, form: form}; const valid = await dataFormRef.value.validate().catch(() => {});
common.handleCloneSubmit(clone); if (!valid) return false;
try {
loading.value = true;
await tempStore(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
return true;
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
}
// 提交 let clone = { operType: operType.value, form: form };
const onSubmit = async () => { common.handleCloneSubmit(clone);
const valid = await dataFormRef.value.validate().catch(() => {}); try {
if (!valid) return false; loading.value = true;
form.status !== DIC_PROP.ORDER_STATUS[0].value ? await addObj(form) : await putObj(form);
let clone = {operType: operType.value, form: form}; useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
common.handleCloneSubmit(clone) return true;
try { } catch (err: any) {
loading.value = true; useMessage().error(err.msg);
form.status !== DIC_PROP.ORDER_STATUS[0].value ? await addObj(form) : await putObj(form); } finally {
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); loading.value = false;
return true; }
} catch (err: any) { };
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getAskLeaveData = (id: string) => {
// 获取数据
loading.value = true
getObj(id).then((res: any) => {
Object.assign(form, res.data)
let clone = {operType: operType.value, form: form};
common.handleClone(clone);
}).finally(() => {
loading.value = false
})
};
// 初始化表单数据
const getAskLeaveData = (id: string) => {
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
let clone = { operType: operType.value, form: form };
common.handleClone(clone);
})
.finally(() => {
loading.value = false;
});
};
</script> </script>

View File

@@ -1,52 +1,51 @@
export default { export default {
flowApplication: { flowApplication: {
index: '#', index: '#',
importflowApplicationTip: 'import FlowApplication', importflowApplicationTip: 'import FlowApplication',
id: 'id', id: 'id',
flowKey: 'flowKey', flowKey: 'flowKey',
icon: 'icon', icon: 'icon',
formName: 'formName', formName: 'formName',
groupName: 'groupName', groupName: 'groupName',
tableName: 'tableName', tableName: 'tableName',
permission: 'permission', permission: 'permission',
remark: 'remark', remark: 'remark',
status: 'status', status: 'status',
version: 'version', version: 'version',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
defFlowId: 'defFlowId', defFlowId: 'defFlowId',
formInfo: 'formInfo', formInfo: 'formInfo',
type: 'type', type: 'type',
subTableName: 'subTableName', subTableName: 'subTableName',
subMainField: 'subMainField', subMainField: 'subMainField',
mainSubProp: 'mainSubProp', mainSubProp: 'mainSubProp',
sort: 'sort', sort: 'sort',
updateUser: 'updateUser', updateUser: 'updateUser',
updateTime: 'updateTime', updateTime: 'updateTime',
delFlag: 'delFlag', delFlag: 'delFlag',
inputIdTip: 'input id', inputIdTip: 'input id',
inputFlowKeyTip: 'input flowKey', inputFlowKeyTip: 'input flowKey',
inputIconTip: 'input icon', inputIconTip: 'input icon',
inputFormNameTip: 'input formName', inputFormNameTip: 'input formName',
inputGroupNameTip: 'input groupName', inputGroupNameTip: 'input groupName',
inputTableNameTip: 'input tableName', inputTableNameTip: 'input tableName',
inputPermissionTip: 'input permission', inputPermissionTip: 'input permission',
inputRemarkTip: 'input remark', inputRemarkTip: 'input remark',
inputStatusTip: 'input status', inputStatusTip: 'input status',
inputVersionTip: 'input version', inputVersionTip: 'input version',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputDefFlowIdTip: 'input defFlowId', inputDefFlowIdTip: 'input defFlowId',
inputFormInfoTip: 'input formInfo', inputFormInfoTip: 'input formInfo',
inputTypeTip: 'input type', inputTypeTip: 'input type',
inputSubTableNameTip: 'input subTableName', inputSubTableNameTip: 'input subTableName',
inputSubMainFieldTip: 'input subMainField', inputSubMainFieldTip: 'input subMainField',
inputMainSubPropTip: 'input mainSubProp', inputMainSubPropTip: 'input mainSubProp',
inputSortTip: 'input sort', inputSortTip: 'input sort',
inputUpdateUserTip: 'input updateUser', inputUpdateUserTip: 'input updateUser',
inputUpdateTimeTip: 'input updateTime', inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag', inputDelFlagTip: 'input delFlag',
},
} };
}

View File

@@ -1,52 +1,51 @@
export default { export default {
flowApplication: { flowApplication: {
index: '#', index: '#',
importflowApplicationTip: '导入办公申请', importflowApplicationTip: '导入办公申请',
id: '主键id', id: '主键id',
flowKey: '流程KEY', flowKey: '流程KEY',
icon: '表单图标', icon: '表单图标',
formName: '表单名称', formName: '表单名称',
groupName: '分组名称', groupName: '分组名称',
tableName: '关联表名称', tableName: '关联表名称',
permission: '操作权限', permission: '操作权限',
remark: '表单备注', remark: '表单备注',
status: '状态', status: '状态',
version: '版本', version: '版本',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
defFlowId: '流程定义ID', defFlowId: '流程定义ID',
formInfo: '表单信息', formInfo: '表单信息',
type: '表单类型', type: '表单类型',
subTableName: '关联子表名称', subTableName: '关联子表名称',
subMainField: '关联主表列名', subMainField: '关联主表列名',
mainSubProp: '关联子表属性', mainSubProp: '关联子表属性',
sort: '排序值', sort: '排序值',
updateUser: '修改人', updateUser: '修改人',
updateTime: '修改时间', updateTime: '修改时间',
delFlag: '删除标', delFlag: '删除标',
inputIdTip: '请输入主键id', inputIdTip: '请输入主键id',
inputFlowKeyTip: '请输入流程KEY', inputFlowKeyTip: '请输入流程KEY',
inputIconTip: '请输入表单图标', inputIconTip: '请输入表单图标',
inputFormNameTip: '请输入表单名称', inputFormNameTip: '请输入表单名称',
inputGroupNameTip: '请输入分组名称', inputGroupNameTip: '请输入分组名称',
inputTableNameTip: '请输入关联表名称', inputTableNameTip: '请输入关联表名称',
inputPermissionTip: '请选择操作角色, 默认所有人都可以发起该流程', inputPermissionTip: '请选择操作角色, 默认所有人都可以发起该流程',
inputRemarkTip: '请输入表单备注', inputRemarkTip: '请输入表单备注',
inputStatusTip: '请输入状态', inputStatusTip: '请输入状态',
inputVersionTip: '请输入版本', inputVersionTip: '请输入版本',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputDefFlowIdTip: '请输入流程定义ID', inputDefFlowIdTip: '请输入流程定义ID',
inputFormInfoTip: '请输入表单信息', inputFormInfoTip: '请输入表单信息',
inputTypeTip: '请选择表单类型', inputTypeTip: '请选择表单类型',
inputSubTableNameTip: '请选择关联子表名称', inputSubTableNameTip: '请选择关联子表名称',
inputSubMainFieldTip: '请输入关联主表列名', inputSubMainFieldTip: '请输入关联主表列名',
inputMainSubPropTip: '请输入关联子表属性', inputMainSubPropTip: '请输入关联子表属性',
inputSortTip: '请输入排序值', inputSortTip: '请输入排序值',
inputUpdateUserTip: '请输入修改人', inputUpdateUserTip: '请输入修改人',
inputUpdateTimeTip: '请输入修改时间', inputUpdateTimeTip: '请输入修改时间',
inputDelFlagTip: '请输入删除标', inputDelFlagTip: '请输入删除标',
},
} };
}

View File

@@ -1,309 +1,296 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div style="background: #f4f4f5; margin-bottom: 10px" class="el_result_layout">
<el-row :gutter="24">
<el-col :span="8">
<el-space class="el-space_container" @click="handleClickHref('TodoJobHash')">
<el-result style="width: 100px; padding: 0">
<template #icon>
<el-icon :size="50" style="color: #409eff"><BellFilled /></el-icon>
</template>
</el-result>
<el-card shadow="never" class="el-card__title__" style="width: 220px; border: none">
<template #header> 待我审批 </template>
<div style="height: 23px; font-size: 30px; font-weight: bold">{{ useFlowJob().jobLen }}</div>
</el-card>
</el-space>
</el-col>
<el-col :span="4" style="padding: 0">
<el-result class="el-result__style__" @click="handleClickHref('RunApplicationHash')" title="我的申请">
<template #icon>
<el-icon :size="40" style="color: #409eff"><Promotion /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0">
<el-result class="el-result__style__" @click="handleClickHref('TodoJobHash', '?belongType=1')" title="抄送我的">
<template #icon>
<el-icon :size="40" style="color: #409eff"><Avatar /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0">
<el-result class="el-result__style__" @click="handleClickHref('SignJobHash')" title="待认领的">
<template #icon>
<el-icon :size="40" style="color: #409eff"><Opportunity /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0 10px 0 0">
<el-result class="el-result__style__" @click="handleClickHref('HiJobHash')" title="我审批的">
<template #icon>
<el-icon :size="40" style="color: #409eff"><Stamp /></el-icon>
</template>
</el-result>
</el-col>
</el-row>
</div>
<div style="background: #f4f4f5; margin-bottom: 10px" class="el_result_layout"> <div class="layout-padding-auto layout-padding-view" style="overflow-y: auto">
<el-row :gutter="24"> <template v-for="(tabs, index) in data.tabsData" :key="index">
<el-col :span="8"> <el-collapse v-model="data.collapse">
<el-space class="el-space_container" @click="handleClickHref('TodoJobHash')"> <el-collapse-item :name="index">
<el-result style="width: 100px; padding: 0;"> <template #title>
<template #icon> <el-icon :size="24" style="margin-right: 8px; color: #409eff">
<el-icon :size="50" style="color: #409EFF;"><BellFilled /></el-icon> <CaretRight v-if="!data.collapse.includes(index)" />
</template> <CaretBottom v-else />
</el-result> </el-icon>
<el-card shadow="never" class="el-card__title__" style="width: 220px; border: none"> <div style="font-size: 14px">{{ tabs.groupName }}</div>
<template #header> 待我审批 </template> </template>
<div style="height: 23px; font-size: 30px; font-weight: bold;"> {{ useFlowJob().jobLen }} </div>
</el-card>
</el-space>
</el-col>
<el-col :span="4" style="padding: 0;">
<el-result class="el-result__style__" @click="handleClickHref('RunApplicationHash')"
title="我的申请">
<template #icon>
<el-icon :size="40" style="color: #409EFF;"><Promotion /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0;">
<el-result class="el-result__style__" @click="handleClickHref('TodoJobHash', '?belongType=1')"
title="抄送我的">
<template #icon>
<el-icon :size="40" style="color: #409EFF;"><Avatar /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0;">
<el-result class="el-result__style__" @click="handleClickHref('SignJobHash')"
title="待认领的">
<template #icon>
<el-icon :size="40" style="color: #409EFF;"><Opportunity /></el-icon>
</template>
</el-result>
</el-col>
<el-col :span="4" style="padding: 0 10px 0 0;">
<el-result class="el-result__style__" @click="handleClickHref('HiJobHash')"
title="我审批的">
<template #icon>
<el-icon :size="40" style="color: #409EFF;"><Stamp /></el-icon>
</template>
</el-result>
</el-col>
</el-row>
</div>
<div class="layout-padding-auto layout-padding-view" style="overflow-y: auto;"> <div class="avue-view__avue-card">
<template v-for="(tabs, index) in data.tabsData" :key="index"> <el-row :span="24" :gutter="20">
<el-collapse v-model="data.collapse"> <el-col v-for="(item, index) in data.tableData.filter((f) => f.groupName === tabs.groupName)" :key="index" :span="6">
<el-collapse-item :name="index"> <div class="avue-card__item">
<template #title> <div class="avue-card__body">
<el-icon :size="24" style="margin-right: 8px; color: #409EFF;"> <div class="avue-card__avatar2">
<CaretRight v-if="!data.collapse.includes(index)" /> <i :class="item.icon" alt="" style="font-size: 38px !important; color: #409eff" />
<CaretBottom v-else /> </div>
</el-icon> <div class="avue-card__detail">
<div style="font-size: 14px;"> {{ tabs.groupName }} </div> <div class="avue-card__title">{{ item.formName }} V{{ item.version }}</div>
</template> <div class="avue-card__info">{{ item.remark }}</div>
</div>
</div>
<div class="avue-card__menu">
<span v-if="item.status === '1'" v-auth="'order_flowapplication_edit'" @click.stop="handleInitiateOrder(item, index)"
>发起工单
</span>
<span v-auth="'order_flowapplication_edit'" @click.stop="openPreview(item.defFlowId)">查看流程图 </span>
</div>
</div>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</template>
<div class="avue-view__avue-card"> <!-- 发起工单 -->
<el-row <json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="handleInitiateOrder">
:span="24" <template v-slot="slotProps" v-if="data.showInitiateOrder">
:gutter="20"> <flow-initiate
<el-col v-for="(item,index) in data.tableData.filter(f => f.groupName === tabs.groupName)" ref="form"
:key="index" v-show="slotProps.currActive === 'form'"
:span="6"> :curr-flow-form="data.currFlowForm"
<div class="avue-card__item"> @handleInitiateOrder="handleInitiateOrder"
<div class="avue-card__body"> ></flow-initiate>
<div class="avue-card__avatar2"> </template>
<i :class="item.icon" alt="" style="font-size: 38px!important;color: #409EFF;"/> <template v-slot="slotProps" v-if="data.showHandleForm">
</div> <custom-form
<div class="avue-card__detail"> ref="form"
<div class="avue-card__title">{{ item.formName }} V{{ item.version }}</div> v-show="slotProps.currActive === 'form'"
<div class="avue-card__info">{{ item.remark }}</div> :curr-job="data.currFlowForm"
</div> @onHandleForm="handleInitiateOrder"
</div> ></custom-form>
<div class="avue-card__menu"> </template>
<span </json-flow-predict>
v-if="item.status === '1'" v-auth="'order_flowapplication_edit'"
@click.stop="handleInitiateOrder(item,index)">发起工单
</span>
<span v-auth="'order_flowapplication_edit'"
@click.stop="openPreview(item.defFlowId)">查看流程图
</span>
</div>
</div>
</el-col>
</el-row>
</div>
</el-collapse-item> <!-- 查看流程图 -->
</el-collapse> <el-drawer class="flow-overflow-drawer" direction="rtl" append-to-body size="90%" v-model="data.showFlowPic">
</template> <flow-photo v-if="data.showFlowPic" :curr-job="data.currFlowForm"></flow-photo>
</el-drawer>
<!-- 发起工单 --> </div>
<json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="handleInitiateOrder"> </div>
<template v-slot="slotProps" v-if="data.showInitiateOrder">
<flow-initiate ref="form" v-show="slotProps.currActive === 'form'" :curr-flow-form="data.currFlowForm"
@handleInitiateOrder="handleInitiateOrder"></flow-initiate>
</template>
<template v-slot="slotProps" v-if="data.showHandleForm">
<custom-form ref="form" v-show="slotProps.currActive === 'form'" :curr-job="data.currFlowForm"
@onHandleForm="handleInitiateOrder"></custom-form>
</template>
</json-flow-predict>
<!-- 查看流程图 -->
<el-drawer
class="flow-overflow-drawer" direction="rtl"
append-to-body size="90%"
v-model="data.showFlowPic"
>
<flow-photo v-if="data.showFlowPic" :curr-job="data.currFlowForm"></flow-photo>
</el-drawer>
</div>
</div>
</template> </template>
<script setup lang="ts" name="systemFlowApplication"> <script setup lang="ts" name="systemFlowApplication">
import * as flowApplication from "/@/api/order/flow-application"; import * as flowApplication from '/@/api/order/flow-application';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import other, {deepClone} from "/@/utils/other"; import other, { deepClone } from '/@/utils/other';
import {useFlowJob} from "/@/flow/stores/flowJob"; import { useFlowJob } from '/@/flow/stores/flowJob';
import {windowLocationHrefParam} from "/@/flow/support/extend"; import { windowLocationHrefParam } from '/@/flow/support/extend';
import {handleCustomForm, vueKey} from "/@/api/order/order-key-vue"; import { handleCustomForm, vueKey } from '/@/api/order/order-key-vue';
// 引入组件 // 引入组件
const FlowPhoto = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/view.vue')); const FlowPhoto = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/view.vue'));
const FlowInitiate = defineAsyncComponent(() => import('./initiate.vue')); const FlowInitiate = defineAsyncComponent(() => import('./initiate.vue'));
const CustomForm = defineAsyncComponent(() => import('/@/flow/components/custom-form/handle.vue')); const CustomForm = defineAsyncComponent(() => import('/@/flow/components/custom-form/handle.vue'));
const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue')); const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue'));
const {t} = useI18n() const { t } = useI18n();
const {proxy} = getCurrentInstance(); const { proxy } = getCurrentInstance();
const data = reactive({ const data = reactive({
tableData: [], tableData: [],
tabsData: [], tabsData: [],
showInitiateOrder: false, showInitiateOrder: false,
showFlowPic: false, showFlowPic: false,
currFlowForm: {}, currFlowForm: {},
showHandleForm: false, showHandleForm: false,
collapse: [0] collapse: [0],
}) });
// 列表查询 // 列表查询
function getList() { function getList() {
flowApplication.listByPerms({}).then(response => { flowApplication.listByPerms({}).then((response) => {
data.tableData = response.data data.tableData = response.data;
data.tabsData = data.tableData.filter((value, i, arr) => data.tabsData = data.tableData.filter((value, i, arr) => arr.findIndex((x) => x.groupName === value.groupName) === i);
arr.findIndex(x => x.groupName === value.groupName) === i });
); }
})
}
function handleInitiateOrder(row, index) { function handleInitiateOrder(row, index) {
if (row === false) { if (row === false) {
openPredict({}, false) openPredict({}, false);
data.showInitiateOrder = false data.showInitiateOrder = false;
data.showHandleForm = false data.showHandleForm = false;
return return;
} }
data.currFlowForm = deepClone(row) data.currFlowForm = deepClone(row);
// 判断是否自定义首页 // 判断是否自定义首页
if (row.path !== vueKey.RunApplicationForm) { if (row.path !== vueKey.RunApplicationForm) {
handleCustomForm(data, row) handleCustomForm(data, row);
data.currFlowForm.operType = 'add' data.currFlowForm.operType = 'add';
data.showHandleForm = true data.showHandleForm = true;
} else { } else {
data.showInitiateOrder = true data.showInitiateOrder = true;
} }
openPredict(row, true) openPredict(row, true);
} }
function openPredict(row, bool) { function openPredict(row, bool) {
proxy.$refs.predict.open(row, bool) proxy.$refs.predict.open(row, bool);
} }
function handleClickHref(hash, param = '') { function handleClickHref(hash, param = '') {
windowLocationHrefParam(hash, param) windowLocationHrefParam(hash, param);
} }
function openPreview(defFlowId) { function openPreview(defFlowId) {
data.currFlowForm = {defFlowId: defFlowId} data.currFlowForm = { defFlowId: defFlowId };
data.showFlowPic = true data.showFlowPic = true;
} }
onMounted(() => { onMounted(() => {
getList() getList();
}) });
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../../flow/components/style/flow-drawer.scss"; @import '../../../flow/components/style/flow-drawer.scss';
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.avue-view__avue-card { .avue-view__avue-card {
width: 100%; width: 100%;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
.avue-card__item:hover { .avue-card__item:hover {
border-color: #409EFF; border-color: #409eff;
} }
.avue-card__item { .avue-card__item {
margin-bottom: 12px; margin-bottom: 12px;
border: 1px solid #e8e8e8; border: 1px solid #e8e8e8;
border-radius: 8px; border-radius: 8px;
background-color: #fff; background-color: #fff;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
color: rgba(0, 0, 0, .65); color: rgba(0, 0, 0, 0.65);
font-size: 14px; font-size: 14px;
font-variant: tabular-nums; font-variant: tabular-nums;
line-height: 1.5; line-height: 1.5;
list-style: none; list-style: none;
-webkit-font-feature-settings: "tnum"; -webkit-font-feature-settings: 'tnum';
font-feature-settings: "tnum"; font-feature-settings: 'tnum';
cursor: pointer; cursor: pointer;
.avue-card__body { .avue-card__body {
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
.avue-card__avatar2 { .avue-card__avatar2 {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 48px; border-radius: 48px;
overflow: hidden; overflow: hidden;
margin-right: 12px; margin-right: 12px;
margin-left: 12px; margin-left: 12px;
} }
.avue-card__detail { .avue-card__detail {
-webkit-box-flex: 1; -webkit-box-flex: 1;
-ms-flex: 1; -ms-flex: 1;
flex: 1; flex: 1;
.avue-card__title { .avue-card__title {
color: rgba(0, 0, 0, .85); color: rgba(0, 0, 0, 0.85);
margin-top: 6px; margin-top: 6px;
margin-bottom: 6px; margin-bottom: 6px;
font-size: 16px; font-size: 16px;
} }
.avue-card__info { .avue-card__info {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
overflow: hidden; overflow: hidden;
height: 64px; height: 64px;
} }
} }
} }
.avue-card__menu { .avue-card__menu {
border-radius: 8px; border-radius: 8px;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
-ms-flex-pack: distribute; -ms-flex-pack: distribute;
justify-content: space-around; justify-content: space-around;
height: 50px; height: 50px;
background: #f7f9fa; background: #f7f9fa;
text-align: center; text-align: center;
line-height: 50px; line-height: 50px;
} }
} }
} }
.el_result_layout { .el_result_layout {
cursor: pointer; cursor: pointer;
.el-space_container { .el-space_container {
width: 100%; width: 100%;
background: white; background: white;
border-radius: 8px; border-radius: 8px;
} }
.el-card__title__:hover { .el-card__title__:hover {
background: #f4f4f5; background: #f4f4f5;
} }
.el-card__title__ { .el-card__title__ {
// 居中不能适应屏幕大小 // 居中不能适应屏幕大小
// display: grid; // display: grid;
// place-items: center; // place-items: center;
margin: 0; margin: 0;
font-size: 20px; font-size: 20px;
} }
.el-result__style__:hover {
background: #f4f4f5;
}
.el-result__style__ {
border-radius: 8px;
background: white;
padding: 10px;
}
}
.el-result__style__:hover {
background: #f4f4f5;
}
.el-result__style__ {
border-radius: 8px;
background: white;
padding: 10px;
}
}
</style> </style>

View File

@@ -1,125 +1,118 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<form-render ref="formCreateRef" :currFlowForm="data.currFlowForm" :initFormPermPrint="initFormPermPrint"> <form-render ref="formCreateRef" :currFlowForm="data.currFlowForm" :initFormPermPrint="initFormPermPrint"> </form-render>
</form-render> </div>
</div> <footer class="el-dialog__footer" v-if="data.submitBtn">
<footer class="el-dialog__footer" v-if="data.submitBtn"> <span class="dialog-footer">
<span class="dialog-footer"> <el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }} </el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ <el-button type="primary" @click="handleTempStore" :disabled="loading">{{ t('jfI18n.temp') }} </el-button>
t('jfI18n.submit') </span>
}} </footer>
</el-button> </div>
<el-button type="primary" @click="handleTempStore" :disabled="loading">{{
t('jfI18n.temp')
}}
</el-button>
</span>
</footer>
</div>
</template> </template>
<script setup lang="ts" name="FlowApplicationInitiate"> <script setup lang="ts" name="FlowApplicationInitiate">
import * as flowApplication from '/@/api/order/flow-application' import * as flowApplication from '/@/api/order/flow-application';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import {deepClone} from "/@/utils/other"; import { deepClone } from '/@/utils/other';
import {setPropsNull} from "/@/flow/support/common"; import { setPropsNull } from '/@/flow/support/common';
import {doInitData, doInitiateForm, doTempStore, initFormMethods, initJobDataByApp} from "../index"; import { doInitData, doInitiateForm, doTempStore, initFormMethods, initJobDataByApp } from '../index';
import {handleFormStartPerm} from "/@/flow/utils/form-perm"; import { handleFormStartPerm } from '/@/flow/utils/form-perm';
import {currFormIsView} from "/@/api/order/order-key-vue"; import { currFormIsView } from '/@/api/order/order-key-vue';
const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue')); const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue'));
const formCreateRef = ref(null) const formCreateRef = ref(null);
const {t} = useI18n(); const { t } = useI18n();
const $emit = defineEmits(['handleInitiateOrder']); const $emit = defineEmits(['handleInitiateOrder']);
const loading = ref(false); const loading = ref(false);
const props = defineProps({ const props = defineProps({
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
} },
}); });
const data = reactive({ const data = reactive({
// 兼容app端监听currFlowForm // 兼容app端监听currFlowForm
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
}, },
submitBtn: true submitBtn: true,
}); });
const $route = useRoute(); const $route = useRoute();
function initJobData() { function initJobData() {
initJobDataByApp($route, handleGetObj, () => { initJobDataByApp($route, handleGetObj, () => {
data.currFlowForm = props.currFlowForm data.currFlowForm = props.currFlowForm;
}) });
} }
function handleGetObj(id) { function handleGetObj(id) {
flowApplication.getObj(id).then(resp => { flowApplication.getObj(id).then((resp) => {
let form = resp.data ? resp.data : {} let form = resp.data ? resp.data : {};
Object.assign(data.currFlowForm, form); Object.assign(data.currFlowForm, form);
}) });
} }
async function submitForm() { async function submitForm() {
await doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t) await doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t);
} }
function handleTempStore() { function handleTempStore() {
doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData) doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData);
} }
const methods = initFormMethods(formCreateRef, data) const methods = initFormMethods(formCreateRef, data);
async function initFormPermPrint(formInfo) { async function initFormPermPrint(formInfo) {
// 处理表单权限 // 处理表单权限
let res = await handleFormStartPerm(null, null, formInfo, data.currFlowForm.defFlowId, null, data.currFlowForm.type) let res = await handleFormStartPerm(null, null, formInfo, data.currFlowForm.defFlowId, null, data.currFlowForm.type);
await currFormIsView(methods, res.elTab, true, res.callback, res.widgetList) await currFormIsView(methods, res.elTab, true, res.callback, res.widgetList);
return res.elTab return res.elTab;
} }
function saveInitData(form) { function saveInitData(form) {
data.currFlowForm.formData = validateNull(form) ? undefined : form data.currFlowForm.formData = validateNull(form) ? undefined : form;
let formJson = deepClone(data.currFlowForm) let formJson = deepClone(data.currFlowForm);
formJson.formData = JSON.stringify(formJson.formData) formJson.formData = JSON.stringify(formJson.formData);
formJson.formId = formJson.id formJson.formId = formJson.id;
setPropsNull(formJson, 'id', 'status') setPropsNull(formJson, 'id', 'status');
return formJson; return formJson;
} }
async function getFormData() { async function getFormData() {
return await doInitData(formCreateRef, saveInitData) return await doInitData(formCreateRef, saveInitData);
} }
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
getFormData, getFormData,
}) });
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currFlowForm.id, () => props.currFlowForm.id,
() => { () => {
initJobData(); initJobData();
} }
); );
onMounted(() => { onMounted(() => {
initJobData() initJobData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,170 +1,176 @@
<template> <template>
<div> <div>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
:disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20">
<el-col :span="12" class="mb20"> <el-form-item :label="t('handoverFlow.type')" prop="type">
<el-form-item :label="t('handoverFlow.type')" prop="type"> <el-select v-model="form.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable disabled>
<el-select v-model="form.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable <el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" :value="item.value"></el-option>
filterable disabled> </el-select>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" </el-form-item>
:value="item.value"></el-option> </el-col>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.receiveDept')" prop="receiveDept"> <el-form-item :label="t('handoverFlow.receiveDept')" prop="receiveDept">
<el-tooltip content="请输入部门名称进行模糊搜索" placement="top"> <el-tooltip content="请输入部门名称进行模糊搜索" placement="top">
<el-select v-model="form.receiveDept" :placeholder="t('handoverFlow.inputReceiveDeptTip')" <el-select
remote :remote-method="remoteMethodDept" :reserve-keyword="false" v-model="form.receiveDept"
clearable filterable disabled> :placeholder="t('handoverFlow.inputReceiveDeptTip')"
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" remote
:value="item.deptId"></el-option> :remote-method="remoteMethodDept"
</el-select> :reserve-keyword="false"
</el-tooltip> clearable
</el-form-item> filterable
</el-col> disabled
>
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.receiveUser')" prop="receiveUser"> <el-form-item :label="t('handoverFlow.receiveUser')" prop="receiveUser">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.receiveUser" :placeholder="t('handoverFlow.inputReceiveUserTip')" <el-select
remote :remote-method="remoteMethodUser" :reserve-keyword="false" v-model="form.receiveUser"
clearable filterable disabled> :placeholder="t('handoverFlow.inputReceiveUserTip')"
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" remote
:value="item.userId"></el-option> :remote-method="remoteMethodUser"
</el-select> :reserve-keyword="false"
</el-tooltip> clearable
</el-form-item> filterable
</el-col> disabled
>
</el-row> <el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-form> </el-select>
<template v-if="operType !== 'view'"> </el-tooltip>
<footer class="el-dialog__footer"> </el-form-item>
<span class="dialog-footer"> </el-col>
<el-button type="primary" @click="methods.handleUpdate" :disabled="loading">{{ </el-row>
$t('common.confirmButtonText') </el-form>
}}</el-button> <template v-if="operType !== 'view'">
</span> <footer class="el-dialog__footer">
</footer> <span class="dialog-footer">
</template> <el-button type="primary" @click="methods.handleUpdate" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</div> </span>
</footer>
</template>
</div>
</template> </template>
<script setup lang="ts" name="HandoverDistributionForm"> <script setup lang="ts" name="HandoverDistributionForm">
import {useMessage} from "/@/hooks/message"; import { useMessage } from '/@/hooks/message';
import {distributePerson, getObj} from '/@/api/order/handover-flow' import { distributePerson, getObj } from '/@/api/order/handover-flow';
import {useI18n} from "vue-i18n" import { useI18n } from 'vue-i18n';
import {rule} from '/@/utils/validate'; import { rule } from '/@/utils/validate';
import {onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey} from "/@/flow/components/convert-name/convert"; import { onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert';
import * as orderVue from "/@/api/order/order-key-vue"; import * as orderVue from '/@/api/order/order-key-vue';
const {t} = useI18n(); const { t } = useI18n();
const emits = defineEmits(["handleJob"]); const emits = defineEmits(['handleJob']);
// 定义变量内容 // 定义变量内容
const dataFormRef = ref(); const dataFormRef = ref();
const loading = ref(false); const loading = ref(false);
const operType = ref(false); const operType = ref(false);
// 定义字典 // 定义字典
const dicData = reactive({}); const dicData = reactive({});
const onLoad = onLoadDicUrl(); const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "receiveDept"}, {key: "receiveUser"}); const onFormLoaded = onFormLoadedUrl({ key: 'receiveDept' }, { key: 'receiveUser' });
onMounted(() => { onMounted(() => {
// onLoad(dicData); // onLoad(dicData);
}); });
function remoteMethodDept(query: string) { function remoteMethodDept(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'deptName', "receiveDept") remoteMethodByKey(query, onLoad, dicData, 'deptName', 'receiveDept');
} }
function remoteMethodUser(query: string) { function remoteMethodUser(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'userName', "receiveUser") remoteMethodByKey(query, onLoad, dicData, 'userName', 'receiveUser');
} }
// 提交表单数据 // 提交表单数据
const form = reactive({ const form = reactive({
type: '', type: '',
receiveDept: '', receiveDept: '',
receiveUser: '', receiveUser: '',
}); });
// 定义校验规则 // 定义校验规则
const dataRules = ref({ const dataRules = ref({
type: [{required: true, message: '交接类型不能为空', trigger: 'blur'}], type: [{ required: true, message: '交接类型不能为空', trigger: 'blur' }],
receiveDept: [{required: true, message: '接收部门不能为空', trigger: 'blur'}], receiveDept: [{ required: true, message: '接收部门不能为空', trigger: 'blur' }],
receiveUser: [{required: true, message: '接收人不能为空', trigger: 'blur'}], receiveUser: [{ required: true, message: '接收人不能为空', trigger: 'blur' }],
}) });
const props = defineProps({ const props = defineProps({
currJob: { currJob: {
type: Object, type: Object,
default: null, default: null,
}, },
currElTab: { currElTab: {
type: Object, type: Object,
default: {}, default: {},
}, },
}); });
const methods = { const methods = {
disableForm() { disableForm() {
operType.value = "view" operType.value = 'view';
}, },
enableForm() { enableForm() {
operType.value = "flow" operType.value = 'flow';
}, },
async initJobData() { async initJobData() {
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, methods.handleUpdate) await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, methods.handleUpdate);
methods.handleGetObj(props.currJob.orderId) methods.handleGetObj(props.currJob.orderId);
}, },
handleGetObj(id) { handleGetObj(id) {
getObj(id).then(response => { getObj(id).then((response) => {
Object.assign(form, response.data) Object.assign(form, response.data);
onFormLoaded(dicData, form); onFormLoaded(dicData, form);
form.runJobId = props.currJob.id form.runJobId = props.currJob.id;
}) });
}, },
async handleUpdate() { async handleUpdate() {
try { try {
loading.value = true; loading.value = true;
// 转岗或离职交接时需要分配接收人来接收未来任务 // 转岗或离职交接时需要分配接收人来接收未来任务
let handoverReason = props.currJob.order.handoverReason; let handoverReason = props.currJob.order.handoverReason;
if (form.isNeedReceive && (handoverReason === '1' || handoverReason === '2') && !form.receiveUser) { if (form.isNeedReceive && (handoverReason === '1' || handoverReason === '2') && !form.receiveUser) {
useMessage().error('转岗或离职交接时需要分配接收人来接收未来任务') useMessage().error('转岗或离职交接时需要分配接收人来接收未来任务');
return return;
} }
await distributePerson(form) await distributePerson(form);
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits);
useMessage().success('操作成功') useMessage().success('操作成功');
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} finally { } finally {
loading.value = false; loading.value = false;
} }
} },
} };
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currJob.id, () => props.currJob.id,
() => { () => {
methods.initJobData(); methods.initJobData();
} }
); );
onMounted(() => { onMounted(() => {
methods.initJobData() methods.initJobData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,226 +1,239 @@
<template> <template>
<el-dialog :title="title" v-model="visible" width="60%" <el-dialog :title="title" v-model="visible" width="60%" :close-on-click-modal="false" draggable>
:close-on-click-modal="false" draggable> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20">
<el-col :span="12" class="mb20"> <el-form-item :label="t('handoverFlow.code')" prop="code">
<el-form-item :label="t('handoverFlow.code')" prop="code"> <el-input v-model="form.code" :placeholder="t('handoverFlow.inputCodeTip')" disabled />
<el-input v-model="form.code" :placeholder="t('handoverFlow.inputCodeTip')" disabled/> </el-form-item>
</el-form-item> </el-col>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.flowKey')" prop="flowKey"> <el-form-item :label="t('handoverFlow.flowKey')" prop="flowKey">
<el-select v-model="form.flowKey" :placeholder="t('handoverFlow.inputFlowKeyTip')" clearable filterable disabled> <el-select v-model="form.flowKey" :placeholder="t('handoverFlow.inputFlowKeyTip')" clearable filterable disabled>
<el-option v-for="(item, index) in cascadeDic.flowKey" :key="index" :label="item.flowName" :value="item.flowKey"></el-option> <el-option v-for="(item, index) in cascadeDic.flowKey" :key="index" :label="item.flowName" :value="item.flowKey"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.type')" prop="type"> <el-form-item :label="t('handoverFlow.type')" prop="type">
<el-select v-model="form.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable> <el-select v-model="form.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.status')" prop="status"> <el-form-item :label="t('handoverFlow.status')" prop="status">
<el-select v-model="form.status" :placeholder="t('handoverFlow.inputStatusTip')" clearable filterable> <el-select v-model="form.status" :placeholder="t('handoverFlow.inputStatusTip')" clearable filterable>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.handoverReason')" prop="handoverReason"> <el-form-item :label="t('handoverFlow.handoverReason')" prop="handoverReason">
<el-select v-model="form.handoverReason" :placeholder="t('handoverFlow.inputHandoverReasonTip')" clearable filterable> <el-select v-model="form.handoverReason" :placeholder="t('handoverFlow.inputHandoverReasonTip')" clearable filterable>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.handoverUserDept')" prop="handoverUserDept"> <el-form-item :label="t('handoverFlow.handoverUserDept')" prop="handoverUserDept">
<el-select v-model="form.handoverUserDept" :placeholder="t('handoverFlow.inputHandoverUserDeptTip')" clearable filterable disabled> <el-select v-model="form.handoverUserDept" :placeholder="t('handoverFlow.inputHandoverUserDeptTip')" clearable filterable disabled>
<el-option v-for="(item, index) in dicData.handoverUserDept" :key="index" :label="item.name" :value="item.deptId"></el-option> <el-option v-for="(item, index) in dicData.handoverUserDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.handoverUser')" prop="handoverUser"> <el-form-item :label="t('handoverFlow.handoverUser')" prop="handoverUser">
<el-select v-model="form.handoverUser" :placeholder="t('handoverFlow.inputHandoverUserTip')" clearable filterable disabled> <el-select v-model="form.handoverUser" :placeholder="t('handoverFlow.inputHandoverUserTip')" clearable filterable disabled>
<el-option v-for="(item, index) in dicData.handoverUser" :key="index" :label="item.name" :value="item.userId"></el-option> <el-option v-for="(item, index) in dicData.handoverUser" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.receiveDept')" prop="receiveDept"> <el-form-item :label="t('handoverFlow.receiveDept')" prop="receiveDept">
<el-tooltip content="请输入部门名称进行模糊搜索" placement="top"> <el-tooltip content="请输入部门名称进行模糊搜索" placement="top">
<el-select v-model="form.receiveDept" :placeholder="t('handoverFlow.inputReceiveDeptTip')" clearable filterable <el-select
remote :remote-method="remoteMethodDept" :reserve-keyword="false"> v-model="form.receiveDept"
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option> :placeholder="t('handoverFlow.inputReceiveDeptTip')"
</el-select> clearable
</el-tooltip> filterable
</el-form-item> remote
</el-col> :remote-method="remoteMethodDept"
:reserve-keyword="false"
>
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.receiveUser')" prop="receiveUser"> <el-form-item :label="t('handoverFlow.receiveUser')" prop="receiveUser">
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
<el-select v-model="form.receiveUser" :placeholder="t('handoverFlow.inputReceiveUserTip')" clearable filterable <el-select
remote :remote-method="remoteMethodUser" :reserve-keyword="false"> v-model="form.receiveUser"
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option> :placeholder="t('handoverFlow.inputReceiveUserTip')"
</el-select> clearable
</el-tooltip> filterable
</el-form-item> remote
</el-col> :remote-method="remoteMethodUser"
:reserve-keyword="false"
>
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverFlow.finishTime')" prop="finishTime"> <el-form-item :label="t('handoverFlow.finishTime')" prop="finishTime">
<el-date-picker type="datetime" :placeholder="t('handoverFlow.inputFinishTimeTip')" v-model="form.finishTime" :value-format="dateTimeStr"></el-date-picker> <el-date-picker
</el-form-item> type="datetime"
</el-col> :placeholder="t('handoverFlow.inputFinishTimeTip')"
v-model="form.finishTime"
</el-row> :value-format="dateTimeStr"
</el-form> ></el-date-picker>
<template #footer v-if="operType !== 'view'"> </el-form-item>
<span class="dialog-footer"> </el-col>
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button> </el-row>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button> </el-form>
</span> <template #footer v-if="operType !== 'view'">
</template> <span class="dialog-footer">
</el-dialog> <el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template> </template>
<script setup lang="ts" name="HandoverFlowDialog"> <script setup lang="ts" name="HandoverFlowDialog">
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/order/handover-flow';
import { useI18n } from 'vue-i18n';
import { rule } from '/@/utils/validate';
import { onCascadeChange, onFormLoadedUrl, onLoadDicUrl, remoteMethodByKey } from '/@/flow/components/convert-name/convert';
const emit = defineEmits(['refresh']);
import { useMessage } from "/@/hooks/message"; const { t } = useI18n();
import { getObj, addObj, putObj } from '/@/api/order/handover-flow'
import { useI18n } from "vue-i18n"
import { rule } from '/@/utils/validate';
import {
onCascadeChange,
onFormLoadedUrl,
onLoadDicUrl,
remoteMethodByKey
} from "/@/flow/components/convert-name/convert";
const emit = defineEmits(['refresh']);
const { t } = useI18n(); // 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const operType = ref(false);
const title = ref('');
// 定义字典
const dicData = reactive({});
const cascadeDic = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'receiveDept' }, { key: 'receiveUser' }, { key: 'handoverUserDept' }, { key: 'handoverUser' });
const onCascade = onCascadeChange(cascadeDic, { key: 'flowInstId', cascades: ['flowKey'] });
onMounted(() => {
// onLoad(dicData);
});
// 定义变量内容 function remoteMethodDept(query: string) {
const dataFormRef = ref(); remoteMethodByKey(query, onLoad, dicData, 'deptName', 'receiveDept');
const visible = ref(false); }
const loading = ref(false);
const operType = ref(false);
const title = ref('');
// 定义字典
const dicData = reactive({});
const cascadeDic = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "receiveDept"}, {key: "receiveUser"}, {key: "handoverUserDept"}, {key: "handoverUser"});
const onCascade = onCascadeChange(cascadeDic, {key: "flowInstId", cascades: ["flowKey"]});
onMounted(() => {
// onLoad(dicData);
});
function remoteMethodDept(query: string) { function remoteMethodUser(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'deptName', "receiveDept") remoteMethodByKey(query, onLoad, dicData, 'userName', 'receiveUser');
} }
function remoteMethodUser(query: string) { // 提交表单数据
remoteMethodByKey(query, onLoad, dicData, 'userName', "receiveUser") const form = reactive({
} code: '',
flowKey: '',
type: '',
status: '',
handoverReason: '',
handoverUser: '',
handoverUserDept: '',
receiveDept: '',
receiveUser: '',
flowInstId: '',
finishTime: '',
});
// 提交表单数据 // 定义校验规则
const form = reactive({ const dataRules = ref({
code: '', code: [{ required: true, message: '编号不能为空', trigger: 'blur' }],
flowKey: '', flowKey: [{ required: true, message: '业务类型不能为空', trigger: 'blur' }],
type: '', type: [{ required: true, message: '交接类型不能为空', trigger: 'blur' }],
status: '', status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
handoverReason: '', handoverReason: [{ required: true, message: '交接原因不能为空', trigger: 'blur' }],
handoverUser: '', receiveDept: [{ required: true, message: '接收部门不能为空', trigger: 'blur' }],
handoverUserDept: '', receiveUser: [{ required: true, message: '接收人不能为空', trigger: 'blur' }],
receiveDept: '', });
receiveUser: '',
flowInstId: '',
finishTime: '',
});
// 定义校验规则 // 打开弹窗
const dataRules = ref({ const openDialog = (type: string, id: string) => {
code: [{required: true, message: '编号不能为空', trigger: 'blur'}], visible.value = true;
flowKey: [{required: true, message: '业务类型不能为空', trigger: 'blur'}], operType.value = type;
type: [{required: true, message: '交接类型不能为空', trigger: 'blur'}], form.id = '';
status: [{required: true, message: '状态不能为空', trigger: 'blur'}],
handoverReason: [{required: true, message: '交接原因不能为空', trigger: 'blur'}],
receiveDept: [{required: true, message: '接收部门不能为空', trigger: 'blur'}],
receiveUser: [{required: true, message: '接收人不能为空', trigger: 'blur'}],
})
// 打开弹窗 if (type === 'add') {
const openDialog = (type: string, id: string) => { title.value = t('common.addBtn');
visible.value = true } else if (type === 'edit') {
operType.value = type; title.value = t('common.editBtn');
form.id = '' } else if (type === 'view') {
title.value = t('common.viewBtn');
}
if (type === 'add') { // 重置表单数据
title.value = t('common.addBtn'); nextTick(() => {
} else if (type === 'edit') { dataFormRef.value?.resetFields();
title.value = t('common.editBtn'); });
} else if (type === 'view') {
title.value = t('common.viewBtn');
}
// 重置表单数据 // 获取HandoverFlow信息
nextTick(() => { if (id) {
dataFormRef.value?.resetFields(); form.id = id;
}); getHandoverFlowData(id);
}
};
// 获取HandoverFlow信息 // 提交
if (id) { const onSubmit = async () => {
form.id = id const valid = await dataFormRef.value.validate().catch(() => {});
getHandoverFlowData(id) if (!valid) return false;
}
};
// 提交 try {
const onSubmit = async () => { loading.value = true;
const valid = await dataFormRef.value.validate().catch(() => {}); form.id ? await putObj(form) : await addObj(form);
if (!valid) return false; useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
try { // 初始化表单数据
loading.value = true; const getHandoverFlowData = (id: string) => {
form.id ? await putObj(form) : await addObj(form); // 获取数据
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); loading.value = true;
visible.value = false; getObj(id)
emit('refresh'); .then((res: any) => {
} catch (err: any) { Object.assign(form, res.data);
useMessage().error(err.msg); onFormLoaded(dicData, form);
} finally { onCascade(form);
loading.value = false; })
} .finally(() => {
}; loading.value = false;
});
};
// 初始化表单数据 // 暴露变量
const getHandoverFlowData = (id: string) => { defineExpose({
// 获取数据 openDialog,
loading.value = true });
getObj(id).then((res: any) => {
Object.assign(form, res.data)
onFormLoaded(dicData, form);
onCascade(form);
}).finally(() => {
loading.value = false
})
};
// 暴露变量
defineExpose({
openDialog
});
</script> </script>

View File

@@ -1,42 +1,41 @@
export default { export default {
handoverFlow: { handoverFlow: {
index: '#', index: '#',
importhandoverFlowTip: 'import HandoverFlow', importhandoverFlowTip: 'import HandoverFlow',
id: 'id', id: 'id',
code: 'code', code: 'code',
flowKey: 'flowKey', flowKey: 'flowKey',
type: 'type', type: 'type',
status: 'status', status: 'status',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
handoverReason: 'handoverReason', handoverReason: 'handoverReason',
handoverUser: 'handoverUser', handoverUser: 'handoverUser',
handoverUserDept: 'handoverUserDept', handoverUserDept: 'handoverUserDept',
receiveDept: 'receiveDept', receiveDept: 'receiveDept',
receiveUser: 'receiveUser', receiveUser: 'receiveUser',
flowInstId: 'flowInstId', flowInstId: 'flowInstId',
finishTime: 'finishTime', finishTime: 'finishTime',
updateUser: 'updateUser', updateUser: 'updateUser',
updateTime: 'updateTime', updateTime: 'updateTime',
delFlag: 'delFlag', delFlag: 'delFlag',
inputIdTip: 'input id', inputIdTip: 'input id',
inputCodeTip: 'input code', inputCodeTip: 'input code',
inputFlowKeyTip: 'input flowKey', inputFlowKeyTip: 'input flowKey',
inputTypeTip: 'input type', inputTypeTip: 'input type',
inputStatusTip: 'input status', inputStatusTip: 'input status',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputHandoverReasonTip: 'input handoverReason', inputHandoverReasonTip: 'input handoverReason',
inputHandoverUserTip: 'input handoverUser', inputHandoverUserTip: 'input handoverUser',
inputHandoverUserDeptTip: 'input handoverUserDept', inputHandoverUserDeptTip: 'input handoverUserDept',
inputReceiveDeptTip: 'input receiveDept', inputReceiveDeptTip: 'input receiveDept',
inputReceiveUserTip: 'input receiveUser', inputReceiveUserTip: 'input receiveUser',
inputFlowInstIdTip: 'input flowInstId', inputFlowInstIdTip: 'input flowInstId',
inputFinishTimeTip: 'input finishTime', inputFinishTimeTip: 'input finishTime',
inputUpdateUserTip: 'input updateUser', inputUpdateUserTip: 'input updateUser',
inputUpdateTimeTip: 'input updateTime', inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag', inputDelFlagTip: 'input delFlag',
},
} };
}

View File

@@ -1,42 +1,41 @@
export default { export default {
handoverFlow: { handoverFlow: {
index: '#', index: '#',
importhandoverFlowTip: '导入交接流程', importhandoverFlowTip: '导入交接流程',
id: 'ID', id: 'ID',
code: '工单编号', code: '工单编号',
flowKey: '流程名称', flowKey: '流程名称',
type: '交接类型', type: '交接类型',
status: '状态', status: '状态',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
handoverReason: '交接原因', handoverReason: '交接原因',
handoverUser: '交接人', handoverUser: '交接人',
handoverUserDept: '交接人部门', handoverUserDept: '交接人部门',
receiveDept: '接收部门', receiveDept: '接收部门',
receiveUser: '接收人', receiveUser: '接收人',
flowInstId: '流程实例ID', flowInstId: '流程实例ID',
finishTime: '完成时间', finishTime: '完成时间',
updateUser: '修改人', updateUser: '修改人',
updateTime: '修改时间', updateTime: '修改时间',
delFlag: '删除标识', delFlag: '删除标识',
inputIdTip: '请输入ID', inputIdTip: '请输入ID',
inputCodeTip: '请输入编号', inputCodeTip: '请输入编号',
inputFlowKeyTip: '请输入业务类型', inputFlowKeyTip: '请输入业务类型',
inputTypeTip: '请输入交接类型', inputTypeTip: '请输入交接类型',
inputStatusTip: '请输入状态', inputStatusTip: '请输入状态',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputHandoverReasonTip: '请输入交接原因', inputHandoverReasonTip: '请输入交接原因',
inputHandoverUserTip: '请输入交接人', inputHandoverUserTip: '请输入交接人',
inputHandoverUserDeptTip: '请输入交接人部门', inputHandoverUserDeptTip: '请输入交接人部门',
inputReceiveDeptTip: '请输入接收部门', inputReceiveDeptTip: '请输入接收部门',
inputReceiveUserTip: '请输入接收人', inputReceiveUserTip: '请输入接收人',
inputFlowInstIdTip: '请输入流程实例ID', inputFlowInstIdTip: '请输入流程实例ID',
inputFinishTimeTip: '请输入完成时间', inputFinishTimeTip: '请输入完成时间',
inputUpdateUserTip: '请输入修改人', inputUpdateUserTip: '请输入修改人',
inputUpdateTimeTip: '请输入修改时间', inputUpdateTimeTip: '请输入修改时间',
inputDelFlagTip: '请输入删除标识', inputDelFlagTip: '请输入删除标识',
},
} };
}

View File

@@ -1,286 +1,310 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('handoverFlow.code')" prop="code"> <el-form-item :label="$t('handoverFlow.code')" prop="code">
<el-input :placeholder="t('handoverFlow.inputCodeTip')" v-model="state.queryForm.code" clearable <el-input :placeholder="t('handoverFlow.inputCodeTip')" v-model="state.queryForm.code" clearable style="max-width: 180px" />
style="max-width: 180px" /> </el-form-item>
</el-form-item> <el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason">
<el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason" > <el-tooltip content="离职 或 转岗 会默认交接全部任务。故可随意选择1条任务发起即可" placement="top">
<el-tooltip content="离职 或 转岗 会默认交接全部任务。故可随意选择1条任务发起即可" placement="top"> <el-select
<el-select v-model="state.queryForm.handoverReason" :placeholder="t('handoverFlow.inputHandoverReasonTip')" clearable filterable style="max-width: 180px"> v-model="state.queryForm.handoverReason"
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option> :placeholder="t('handoverFlow.inputHandoverReasonTip')"
</el-select> clearable
</el-tooltip> filterable
</el-form-item> style="max-width: 180px"
<el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept" > >
<el-tooltip content="请输入部门名称进行模糊搜索" placement="top"> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option>
<el-select v-model="state.queryForm.receiveDept" :placeholder="t('handoverFlow.inputReceiveDeptTip')" clearable filterable </el-select>
remote :remote-method="remoteMethodDept" :reserve-keyword="false" </el-tooltip>
style="max-width: 180px"> </el-form-item>
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option> <el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept">
</el-select> <el-tooltip content="请输入部门名称进行模糊搜索" placement="top">
</el-tooltip> <el-select
</el-form-item> v-model="state.queryForm.receiveDept"
<el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser" > :placeholder="t('handoverFlow.inputReceiveDeptTip')"
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> clearable
<el-select v-model="state.queryForm.receiveUser" :placeholder="t('handoverFlow.inputReceiveUserTip')" clearable filterable filterable
remote :remote-method="remoteMethodUser" :reserve-keyword="false" remote
style="max-width: 180px"> :remote-method="remoteMethodDept"
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option> :reserve-keyword="false"
</el-select> style="max-width: 180px"
</el-tooltip> >
</el-form-item> <el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
<el-form-item :label="$t('handoverFlow.type')" prop="type" > </el-select>
<el-select v-model="state.queryForm.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable style="max-width: 180px" </el-tooltip>
@change="typeChange"> </el-form-item>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :disabled="DIC_PROP.HANDOVER_TYPE[1].value === item.value" <el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser">
:key="index" :label="item.label" :value="item.value"></el-option> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
</el-select> <el-select
</el-form-item> v-model="state.queryForm.receiveUser"
<el-form-item> :placeholder="t('handoverFlow.inputReceiveUserTip')"
<el-button icon="search" type="primary" @click="getDataList"> clearable
{{ $t('common.queryBtn') }} filterable
</el-button> remote
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button> :remote-method="remoteMethodUser"
</el-form-item> :reserve-keyword="false"
</el-form> style="max-width: 180px"
</el-row> >
<el-row> <el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option>
<div class="mb8" style="width: 100%"> </el-select>
<el-button icon="Promotion" type="primary" class="ml10" @click="handleInitiate" </el-tooltip>
:loading="state.loading" </el-form-item>
v-auth="'order_handoverflow_add'"> <el-form-item :label="$t('handoverFlow.type')" prop="type">
{{ $t('jfI18n.initHandover') }} <el-select
</el-button> v-model="state.queryForm.type"
<right-toolbar v-model:showSearch="showSearch" :export="'order_handoverflow_export'" :placeholder="t('handoverFlow.inputTypeTip')"
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" clearable
@queryTable="getDataList"></right-toolbar> filterable
</div> style="max-width: 180px"
</el-row> @change="typeChange"
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" >
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> <el-option
<el-table-column type="selection" width="40" align="center" /> v-for="(item, index) in DIC_PROP.HANDOVER_TYPE"
<el-table-column type="index" :label="t('handoverFlow.index')" width="40" /> :disabled="DIC_PROP.HANDOVER_TYPE[1].value === item.value"
<el-table-column prop="code" :label="t('handoverFlow.code')" show-overflow-tooltip/> :key="index"
<el-table-column prop="flowKey" :label="t('handoverFlow.flowKey')" show-overflow-tooltip> :label="item.label"
<template #default="scope"> :value="item.value"
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" ></el-option>
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name> </el-select>
</template> </el-form-item>
</el-table-column> <el-form-item>
<el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip> <el-button icon="search" type="primary" @click="getDataList">
<template #default="scope"> {{ $t('common.queryBtn') }}
<convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" </el-button>
:valueKey="'id'" :showKey="'jobName'"></convert-name> <el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</template> </el-form-item>
</el-table-column> </el-form>
<el-table-column prop="type" :label="t('handoverFlow.type')" show-overflow-tooltip> </el-row>
<template #default="scope"> <el-row>
<dict-tag :options="DIC_PROP.HANDOVER_TYPE" :value="scope.row.type ? scope.row.type : state.queryForm.type"></dict-tag> <div class="mb8" style="width: 100%">
</template> <el-button icon="Promotion" type="primary" class="ml10" @click="handleInitiate" :loading="state.loading" v-auth="'order_handoverflow_add'">
</el-table-column> {{ $t('jfI18n.initHandover') }}
<el-table-column prop="status" :label="t('handoverFlow.status')" show-overflow-tooltip> </el-button>
<template #default="scope"> <right-toolbar
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag> v-model:showSearch="showSearch"
</template> :export="'order_handoverflow_export'"
</el-table-column> @exportExcel="exportExcel"
<el-table-column prop="createUser" :label="t('handoverFlow.createUser')" show-overflow-tooltip> class="ml10"
<template #default="scope"> style="float: right; margin-right: 20px"
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" @queryTable="getDataList"
:valueKey="'userId'" :showKey="'name'"></convert-name> ></right-toolbar>
</template> </div>
</el-table-column> </el-row>
<el-table-column prop="createTime" :label="t('handoverFlow.createTime')" show-overflow-tooltip/> <el-table
</el-table> :data="state.dataList"
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" /> v-loading="state.loading"
</div> style="width: 100%"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" :label="t('handoverFlow.index')" width="40" />
<el-table-column prop="code" :label="t('handoverFlow.code')" show-overflow-tooltip />
<el-table-column prop="flowKey" :label="t('handoverFlow.flowKey')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" :valueKey="'id'" :showKey="'jobName'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="type" :label="t('handoverFlow.type')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_TYPE" :value="scope.row.type ? scope.row.type : state.queryForm.type"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('handoverFlow.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="createUser" :label="t('handoverFlow.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('handoverFlow.createTime')" show-overflow-tooltip />
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-dialog <el-dialog v-if="data.showNodeHandover" append-to-body v-model="data.showNodeHandover" top="20px" width="80%" :title="data.handoverTitle">
v-if="data.showNodeHandover" <handover-node-record
append-to-body ref="node"
v-model="data.showNodeHandover" :selections="data.selections"
top="20px" :handover-form="data.handoverForm"
width="80%" @onHandoverFlow="onHandoverFlow"
:title="data.handoverTitle"> ></handover-node-record>
<handover-node-record </el-dialog>
ref="node" </div>
:selections="data.selections"
:handover-form="data.handoverForm"
@onHandoverFlow="onHandoverFlow"></handover-node-record>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="systemHandoverFlow"> <script setup lang="ts" name="systemHandoverFlow">
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onLoadDicUrl, onLoaded, remoteMethodByKey} from "/@/flow/components/convert-name/convert"; import { onLoadDicUrl, onLoaded, remoteMethodByKey } from '/@/flow/components/convert-name/convert';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import {DIC_PROP} from "/@/flow/support/dict-prop"; import { DIC_PROP } from '/@/flow/support/dict-prop';
import * as handoverFlow from "/@/api/order/handover-flow"; import * as handoverFlow from '/@/api/order/handover-flow';
import * as runJob from "/@/api/jsonflow/run-job"; import * as runJob from '/@/api/jsonflow/run-job';
// 引入组件 // 引入组件
const HandoverNodeRecord = defineAsyncComponent(() => import('/@/views/order/handover-node-record/initiate.vue')); const HandoverNodeRecord = defineAsyncComponent(() => import('/@/views/order/handover-node-record/initiate.vue'));
const { t } = useI18n() const { t } = useI18n();
// 定义查询字典 // 定义查询字典
const dicData = reactive({}); const dicData = reactive({});
const onLoad = onLoadDicUrl(); const onLoad = onLoadDicUrl();
onMounted(() => { onMounted(() => {
// onLoad(dicData); // onLoad(dicData);
}); });
function remoteMethodDept(query: string) { function remoteMethodDept(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'deptName', "receiveDept") remoteMethodByKey(query, onLoad, dicData, 'deptName', 'receiveDept');
} }
function remoteMethodUser(query: string) { function remoteMethodUser(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'userName', "receiveUser") remoteMethodByKey(query, onLoad, dicData, 'userName', 'receiveUser');
} }
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: { queryForm: {
type: DIC_PROP.HANDOVER_TYPE[0].value, type: DIC_PROP.HANDOVER_TYPE[0].value,
status: DIC_PROP.HANDOVER_STATUS[1].value status: DIC_PROP.HANDOVER_STATUS[1].value,
}, },
pageList: runJob.fetchNodeHandover, pageList: runJob.fetchNodeHandover,
onLoaded: onLoaded({key: "createUser"}, {key: "flowInstId"}, {key: "runJobId"}), onLoaded: onLoaded({ key: 'createUser' }, { key: 'flowInstId' }, { key: 'runJobId' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
data.selections = [] data.selections = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/handover-flow/export', state.queryForm, 'handover-flow.xlsx') downBlobFile('/order/handover-flow/export', state.queryForm, 'handover-flow.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
data.selections = objs data.selections = objs;
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await handoverFlow.delObjs(ids); await handoverFlow.delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
const data = reactive({ const data = reactive({
selections: [], selections: [],
showNodeHandover: false, showNodeHandover: false,
handoverTitle: null, handoverTitle: null,
handoverForm: null, handoverForm: null,
}) });
function fetchListFun(fetchList, params) { function fetchListFun(fetchList, params) {
if (!params.status || !params.type) { if (!params.status || !params.type) {
useMessage().info('请选择必填交接条件') useMessage().info('请选择必填交接条件');
return return;
} }
state.pageList = fetchList state.pageList = fetchList;
getDataList() getDataList();
} }
function typeChange() { function typeChange() {
let type = state.queryForm.type let type = state.queryForm.type;
let handoverType = DIC_PROP.HANDOVER_TYPE let handoverType = DIC_PROP.HANDOVER_TYPE;
if (type === handoverType[0].value) { if (type === handoverType[0].value) {
// 任务交接 // 任务交接
fetchListFun(runJob.fetchNodeHandover, state.queryForm) fetchListFun(runJob.fetchNodeHandover, state.queryForm);
} else { } else {
// 公共交接 TODO 需自行扩展自身业务,参考任务交接 // 公共交接 TODO 需自行扩展自身业务,参考任务交接
// fetchListFun(company.fetchCommonHandover, state.queryForm) // fetchListFun(company.fetchCommonHandover, state.queryForm)
} }
handleSelectionChange([]) handleSelectionChange([]);
} }
function handleInitiate() { function handleInitiate() {
const type = state.queryForm.type const type = state.queryForm.type;
if (validateFormInfo(type)) { if (validateFormInfo(type)) {
return; return;
} }
data.handoverTitle = DIC_PROP.HANDOVER_TYPE.filter(f => f.value === type)[0].label data.handoverTitle = DIC_PROP.HANDOVER_TYPE.filter((f) => f.value === type)[0].label;
// 转岗或离职,则全部交接 // 转岗或离职,则全部交接
if (validateNull(data.selections)) { if (validateNull(data.selections)) {
useMessage().info('请选择交接项') useMessage().info('请选择交接项');
return return;
} }
data.handoverForm = Object.assign({}, state.queryForm) data.handoverForm = Object.assign({}, state.queryForm);
data.handoverForm.code = null data.handoverForm.code = null;
data.showNodeHandover = true data.showNodeHandover = true;
} }
function validateFormInfo(type) { function validateFormInfo(type) {
const handoverReason = state.queryForm.handoverReason const handoverReason = state.queryForm.handoverReason;
if (!handoverReason) { if (!handoverReason) {
useMessage().info('请选择交接原因') useMessage().info('请选择交接原因');
return true return true;
} }
if (!type) { if (!type) {
useMessage().info('请选择交接类型') useMessage().info('请选择交接类型');
return true return true;
} }
if (!state.queryForm.status) { if (!state.queryForm.status) {
useMessage().info('请选择交接状态') useMessage().info('请选择交接状态');
return true return true;
} }
if (!state.queryForm.receiveDept) { if (!state.queryForm.receiveDept) {
useMessage().info('请选择接收部门') useMessage().info('请选择接收部门');
return true return true;
} }
if (!state.queryForm.receiveUser) { if (!state.queryForm.receiveUser) {
useMessage().info('请选择接收人') useMessage().info('请选择接收人');
return true return true;
} }
return false return false;
} }
function onHandoverFlow(type) { function onHandoverFlow(type) {
getDataList() getDataList();
data.showNodeHandover = false data.showNodeHandover = false;
} }
onMounted(() => { onMounted(() => {
state.loading = false state.loading = false;
}); });
</script> </script>

View File

@@ -1,267 +1,296 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('handoverFlow.code')" prop="code" > <el-form-item :label="$t('handoverFlow.code')" prop="code">
<el-input :placeholder="t('handoverFlow.inputCodeTip')" v-model="state.queryForm.code" clearable <el-input :placeholder="t('handoverFlow.inputCodeTip')" v-model="state.queryForm.code" clearable style="max-width: 180px" />
style="max-width: 180px" /> </el-form-item>
</el-form-item> <el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason">
<el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason" > <el-select
<el-select v-model="state.queryForm.handoverReason" :placeholder="t('handoverFlow.inputHandoverReasonTip')" clearable filterable style="max-width: 180px"> v-model="state.queryForm.handoverReason"
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option> :placeholder="t('handoverFlow.inputHandoverReasonTip')"
</el-select> clearable
</el-form-item> filterable
<el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept" > style="max-width: 180px"
<el-tooltip content="请输入部门名称进行模糊搜索" placement="top"> >
<el-select v-model="state.queryForm.receiveDept" :placeholder="t('handoverFlow.inputReceiveDeptTip')" clearable filterable <el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option>
remote :remote-method="remoteMethodDept" :reserve-keyword="false" </el-select>
style="max-width: 180px"> </el-form-item>
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option> <el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept">
</el-select> <el-tooltip content="请输入部门名称进行模糊搜索" placement="top">
</el-tooltip> <el-select
</el-form-item> v-model="state.queryForm.receiveDept"
<el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser" > :placeholder="t('handoverFlow.inputReceiveDeptTip')"
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> clearable
<el-select v-model="state.queryForm.receiveUser" :placeholder="t('handoverFlow.inputReceiveUserTip')" clearable filterable filterable
remote :remote-method="remoteMethodUser" :reserve-keyword="false" remote
style="max-width: 180px"> :remote-method="remoteMethodDept"
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option> :reserve-keyword="false"
</el-select> style="max-width: 180px"
</el-tooltip> >
</el-form-item> <el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
<el-form-item :label="$t('handoverFlow.type')" prop="type" > </el-select>
<el-select v-model="state.queryForm.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable style="max-width: 180px"> </el-tooltip>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :disabled="DIC_PROP.HANDOVER_TYPE[1].value === item.value" </el-form-item>
:key="index" :label="item.label" :value="item.value"></el-option> <el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser">
</el-select> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
</el-form-item> <el-select
<el-form-item :label="$t('handoverFlow.status')" prop="status" > v-model="state.queryForm.receiveUser"
<el-select v-model="state.queryForm.status" :placeholder="t('handoverFlow.inputStatusTip')" clearable filterable style="max-width: 180px"> :placeholder="t('handoverFlow.inputReceiveUserTip')"
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> clearable
</el-select> filterable
</el-form-item> remote
<el-form-item> :remote-method="remoteMethodUser"
<el-button icon="search" type="primary" @click="getDataList"> :reserve-keyword="false"
{{ $t('common.queryBtn') }} style="max-width: 180px"
</el-button> >
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button> <el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-form-item> </el-select>
</el-form> </el-tooltip>
</el-row> </el-form-item>
<el-row> <el-form-item :label="$t('handoverFlow.type')" prop="type">
<div class="mb8" style="width: 100%"> <el-select v-model="state.queryForm.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable style="max-width: 180px">
<el-option
v-for="(item, index) in DIC_PROP.HANDOVER_TYPE"
:disabled="DIC_PROP.HANDOVER_TYPE[1].value === item.value"
:key="index"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('handoverFlow.status')" prop="status">
<el-select v-model="state.queryForm.status" :placeholder="t('handoverFlow.inputStatusTip')" clearable filterable style="max-width: 180px">
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-tooltip placement="top">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
class="ml10"
v-auth="'order_handoverflow_del'"
@click="handleDelete(selectObjs)"
>
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <right-toolbar
<template #content> v-model:showSearch="showSearch"
{{ $t('common.delBtn') }} :export="'order_handoverflow_export'"
</template> @exportExcel="exportExcel"
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" class="ml10"
v-auth="'order_handoverflow_del'" @click="handleDelete(selectObjs)"> style="float: right; margin-right: 20px"
</el-button> @queryTable="getDataList"
</el-tooltip> ></right-toolbar>
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
style="width: 100%"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" :label="t('handoverFlow.index')" width="40" />
<el-table-column prop="code" :label="t('handoverFlow.code')" show-overflow-tooltip />
<el-table-column prop="flowKey" :label="t('handoverFlow.flowKey')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="receiveDept" :label="t('handoverFlow.receiveDept')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.receiveDept" :value="scope.row.receiveDept" :valueKey="'deptId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="receiveUser" :label="t('handoverFlow.receiveUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.receiveUser" :value="scope.row.receiveUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="type" :label="t('handoverFlow.type')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_TYPE" :value="scope.row.type"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('handoverFlow.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="createUser" :label="t('handoverFlow.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('handoverFlow.createTime')" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="120">
<template #default="scope">
<el-tooltip placement="top">
<template #content>
{{ $t('common.viewBtn') }}
</template>
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)"> </el-button>
</el-tooltip>
<right-toolbar v-model:showSearch="showSearch" :export="'order_handoverflow_export'" <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[2].value">
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" <template #content>
@queryTable="getDataList"></right-toolbar> {{ $t('jfI18n.recallBtn') }}
</div> </template>
</el-row> <el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" </el-tooltip>
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[0].value">
<el-table-column type="selection" width="40" align="center" /> <template #content>
<el-table-column type="index" :label="t('handoverFlow.index')" width="40" /> {{ $t('jfI18n.resetBtn') }}
<el-table-column prop="code" :label="t('handoverFlow.code')" show-overflow-tooltip/> </template>
<el-table-column prop="flowKey" :label="t('handoverFlow.flowKey')" show-overflow-tooltip> <el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
<template #default="scope"> </el-tooltip>
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey"
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="receiveDept" :label="t('handoverFlow.receiveDept')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.receiveDept" :value="scope.row.receiveDept"
:valueKey="'deptId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="receiveUser" :label="t('handoverFlow.receiveUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.receiveUser" :value="scope.row.receiveUser"
:valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="type" :label="t('handoverFlow.type')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_TYPE" :value="scope.row.type"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('handoverFlow.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="createUser" :label="t('handoverFlow.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser"
:valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('handoverFlow.createTime')" show-overflow-tooltip/>
<el-table-column :label="$t('common.action')" width="120">
<template #default="scope">
<el-tooltip placement="top">
<template #content>
{{ $t('common.viewBtn') }}
</template>
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)">
</el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[2].value"> <el-tooltip
<template #content> placement="top"
{{ $t('jfI18n.recallBtn') }} v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value || scope.row.status === DIC_PROP.ORDER_STATUS[0].value"
</template> >
<el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> <template #content>
</el-button> {{ $t('common.editBtn') }}
</el-tooltip> </template>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[0].value"> <el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)"> </el-button>
<template #content> </el-tooltip>
{{ $t('jfI18n.resetBtn') }} <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value">
</template> <template #content>
<el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)"> {{ $t('common.delBtn') }}
</el-button> </template>
</el-tooltip> <el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value || scope.row.status===DIC_PROP.ORDER_STATUS[0].value"> <!-- 编辑新增 -->
<template #content> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
{{ $t('common.editBtn') }} </div>
</template>
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)">
</el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template> </template>
<script setup lang="ts" name="systemHandoverFlowView"> <script setup lang="ts" name="systemHandoverFlowView">
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from "/@/api/order/handover-flow"; import { fetchList, delObjs } from '/@/api/order/handover-flow';
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onLoadDicUrl, onLoaded, remoteMethodByKey} from "/@/flow/components/convert-name/convert"; import { onLoadDicUrl, onLoaded, remoteMethodByKey } from '/@/flow/components/convert-name/convert';
import {recallReset} from "/@/api/jsonflow/run-flow"; import { recallReset } from '/@/api/jsonflow/run-flow';
import {DIC_PROP} from "/@/flow/support/dict-prop"; import { DIC_PROP } from '/@/flow/support/dict-prop';
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { t } = useI18n() const { t } = useI18n();
// 定义查询字典 // 定义查询字典
const dicData = reactive({}); const dicData = reactive({});
const onLoad = onLoadDicUrl(); const onLoad = onLoadDicUrl();
onMounted(() => { onMounted(() => {
// onLoad(dicData); // onLoad(dicData);
}); });
function remoteMethodDept(query: string) { function remoteMethodDept(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'deptName', "receiveDept") remoteMethodByKey(query, onLoad, dicData, 'deptName', 'receiveDept');
} }
function remoteMethodUser(query: string) { function remoteMethodUser(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'userName', "receiveUser") remoteMethodByKey(query, onLoad, dicData, 'userName', 'receiveUser');
} }
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: fetchList, pageList: fetchList,
onLoaded: onLoaded({key: "createUser"}, {key: "receiveUser"}, {key: "flowInstId"}, {key: "receiveDept"}), onLoaded: onLoaded({ key: 'createUser' }, { key: 'receiveUser' }, { key: 'flowInstId' }, { key: 'receiveDept' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/handover-flow/export', state.queryForm, 'handover-flow.xlsx') downBlobFile('/order/handover-flow/export', state.queryForm, 'handover-flow.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
function handleRecallReset(row) { function handleRecallReset(row) {
let params = {id: row.flowInstId, flowKey: row.flowKey, status: row.status} let params = { id: row.flowInstId, flowKey: row.flowKey, status: row.status };
if (row.status === DIC_PROP.ORDER_STATUS[0].value) { if (row.status === DIC_PROP.ORDER_STATUS[0].value) {
recallReset(params).then(() => { recallReset(params).then(() => {
useMessage().success('重发成功'); useMessage().success('重发成功');
getDataList(); getDataList();
}) });
return return;
} }
useMessageBox().confirm('是否确认要撤回该工单?') useMessageBox()
.then(() => { .confirm('是否确认要撤回该工单?')
return recallReset(params) .then(() => {
}).then(() => { return recallReset(params);
useMessage().success('撤回成功') })
getDataList(); .then(() => {
}) useMessage().success('撤回成功');
} getDataList();
});
}
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await delObjs(ids); await delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
</script> </script>

View File

@@ -1,439 +1,498 @@
<template> <template>
<div> <div>
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason"> <el-form-item :label="$t('handoverFlow.handoverReason')" prop="handoverReason">
<el-select v-model="state.queryForm.handoverReason" <el-select
:placeholder="t('handoverFlow.inputHandoverReasonTip')" clearable filterable v-model="state.queryForm.handoverReason"
style="max-width: 180px"> :placeholder="t('handoverFlow.inputHandoverReasonTip')"
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" clearable
:label="item.label" :value="item.value"></el-option> filterable
</el-select> style="max-width: 180px"
</el-form-item> >
<el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept"> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_REASON" :key="index" :label="item.label" :value="item.value"></el-option>
<el-tooltip content="请输入部门名称进行模糊搜索" placement="top"> </el-select>
<el-select v-model="state.queryForm.receiveDept" </el-form-item>
:placeholder="t('handoverFlow.inputReceiveDeptTip')" clearable filterable <el-form-item :label="$t('handoverFlow.receiveDept')" prop="receiveDept">
remote :remote-method="remoteMethodDept" :reserve-keyword="false" <el-tooltip content="请输入部门名称进行模糊搜索" placement="top">
style="max-width: 180px"> <el-select
<el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" v-model="state.queryForm.receiveDept"
:value="item.deptId"></el-option> :placeholder="t('handoverFlow.inputReceiveDeptTip')"
</el-select> clearable
</el-tooltip> filterable
</el-form-item> remote
<el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser"> :remote-method="remoteMethodDept"
<el-tooltip content="请输入用户名称进行模糊搜索" placement="top"> :reserve-keyword="false"
<el-select v-model="state.queryForm.receiveUser" style="max-width: 180px"
:placeholder="t('handoverFlow.inputReceiveUserTip')" clearable filterable >
remote :remote-method="remoteMethodUser" :reserve-keyword="false" <el-option v-for="(item, index) in dicData.receiveDept" :key="index" :label="item.name" :value="item.deptId"></el-option>
style="max-width: 180px"> </el-select>
<el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" </el-tooltip>
:value="item.userId"></el-option> </el-form-item>
</el-select> <el-form-item :label="$t('handoverFlow.receiveUser')" prop="receiveUser">
</el-tooltip> <el-tooltip content="请输入用户名称进行模糊搜索" placement="top">
</el-form-item> <el-select
<el-form-item :label="$t('handoverFlow.type')" prop="type"> v-model="state.queryForm.receiveUser"
<el-select v-model="state.queryForm.type" :placeholder="t('handoverFlow.inputTypeTip')" :placeholder="t('handoverFlow.inputReceiveUserTip')"
clearable filterable style="max-width: 180px"> clearable
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" filterable
:value="item.value"></el-option> remote
</el-select> :remote-method="remoteMethodUser"
</el-form-item> :reserve-keyword="false"
<el-form-item :label="$t('handoverNodeRecord.status')" prop="status"> style="max-width: 180px"
<el-select v-model="state.queryForm.status" >
:placeholder="t('handoverNodeRecord.inputStatusTip')" clearable filterable <el-option v-for="(item, index) in dicData.receiveUser" :key="index" :label="item.name" :value="item.userId"></el-option>
style="max-width: 180px"> </el-select>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" </el-tooltip>
:label="item.label" :value="item.value"></el-option> </el-form-item>
</el-select> <el-form-item :label="$t('handoverFlow.type')" prop="type">
</el-form-item> <el-select v-model="state.queryForm.type" :placeholder="t('handoverFlow.inputTypeTip')" clearable filterable style="max-width: 180px">
</el-form> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_TYPE" :key="index" :label="item.label" :value="item.value"></el-option>
</el-row> </el-select>
<el-row> </el-form-item>
<div class="mb8" style="width: 100%"> <el-form-item :label="$t('handoverNodeRecord.status')" prop="status">
<el-button icon="edit-pen" type="primary" class="ml10" @click="methods.handleUpdateFlow" <el-select
:loading="state.loading" v-if="state.queryForm.retStatus === '1'" v-model="state.queryForm.status"
v-auth="'order_handovernoderecord_edit'"> :placeholder="t('handoverNodeRecord.inputStatusTip')"
{{ $t('jfI18n.updateFlow') }} clearable
</el-button> filterable
<el-button icon="RefreshLeft" type="primary" class="ml10" @click="methods.handleCheckToReject" style="max-width: 180px"
:loading="state.loading" v-if="data.isCheckToReject" >
v-auth="'order_handovernoderecord_edit'"> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
{{ $t('jfI18n.rejectBtn') }} </el-select>
</el-button> </el-form-item>
<el-button icon="CircleCheck" type="primary" class="ml10" @click="methods.handleConfirmReceive" </el-form>
:loading="state.loading" v-if="data.isConfirmReceive" </el-row>
v-auth="'order_handovernoderecord_edit'"> <el-row>
{{ $t('jfI18n.receiveBtn') }} <div class="mb8" style="width: 100%">
</el-button> <el-button
</div> icon="edit-pen"
</el-row> type="primary"
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" class="ml10"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> @click="methods.handleUpdateFlow"
<el-table-column type="selection" width="40" align="center" v-if="data.selection"/> :loading="state.loading"
<el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40"/> v-if="state.queryForm.retStatus === '1'"
<el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip> v-auth="'order_handovernoderecord_edit'"
<template #default="scope"> >
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" {{ $t('jfI18n.updateFlow') }}
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name> </el-button>
</template> <el-button
</el-table-column> icon="RefreshLeft"
<el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip> type="primary"
<template #default="scope"> class="ml10"
<convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" @click="methods.handleCheckToReject"
:valueKey="'userId'" :showKey="'name'"></convert-name> :loading="state.loading"
</template> v-if="data.isCheckToReject"
</el-table-column> v-auth="'order_handovernoderecord_edit'"
<el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip> >
<template #default="scope"> {{ $t('jfI18n.rejectBtn') }}
<convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" </el-button>
:valueKey="'id'" :showKey="'jobName'"></convert-name> <el-button
</template> icon="CircleCheck"
</el-table-column> type="primary"
<el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip/> class="ml10"
<el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip> @click="methods.handleConfirmReceive"
<template #default="scope"> :loading="state.loading"
<el-input :disabled="!scope.row.$cellEdit" v-model="scope.row.todoList" type="textarea" v-if="data.isConfirmReceive"
:placeholder="t('handoverNodeRecord.inputTodoListTip')"/> v-auth="'order_handovernoderecord_edit'"
</template> >
</el-table-column> {{ $t('jfI18n.receiveBtn') }}
<el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip> </el-button>
<template #default="scope"> </div>
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag> </el-row>
</template> <el-table
</el-table-column> :data="state.dataList"
<el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip> v-loading="state.loading"
<template #default="scope"> style="width: 100%"
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" @selection-change="handleSelectionChange"
:valueKey="'userId'" :showKey="'name'"></convert-name> @sort-change="sortChangeHandle"
</template> >
</el-table-column> <el-table-column type="selection" width="40" align="center" v-if="data.selection" />
<el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip/> <el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40" />
<el-table-column :label="$t('common.action')" width="200" v-if="data.menu"> <el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-button text type="primary" icon="view" v-if="!scope.row.$cellEdit" <convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
@click="formDialogRef.openDialog('view', scope.row.id)" </template>
v-auth="'order_handovernoderecord_view'"> </el-table-column>
{{ $t('common.viewBtn') }} <el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip>
</el-button> <template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'order_handovernoderecord_edit'" v-if="!scope.row.$cellEdit" <convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" :valueKey="'userId'" :showKey="'name'"></convert-name>
@click="methods.handleEdit(scope.row, scope.$index, true)">{{ $t('common.editBtn') }} </template>
</el-button> </el-table-column>
<el-button icon="Check" text type="primary" v-auth="'order_handovernoderecord_add'" v-if="scope.row.$cellEdit" <el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip>
@click="methods.handleUpdate(scope.row, scope.$index)">{{ $t('jfI18n.saveBtn') }} <template #default="scope">
</el-button> <convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" :valueKey="'id'" :showKey="'jobName'"></convert-name>
<el-button icon="Close" text type="primary" v-auth="'order_handovernoderecord_edit'" v-if="scope.row.$cellEdit" </template>
@click="methods.handleEdit(scope.row, scope.$index, false)">{{ $t('jfI18n.cancelBtn') }} </el-table-column>
</el-button> <el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip />
<el-button icon="delete" text type="primary" v-auth="'order_handovernoderecord_del'" <el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip>
@click="handleDelete(scope.row.id, scope.$index)">{{ <template #default="scope">
$t('common.delBtn') <el-input
}} :disabled="!scope.row.$cellEdit"
</el-button> v-model="scope.row.todoList"
</template> type="textarea"
</el-table-column> :placeholder="t('handoverNodeRecord.inputTodoListTip')"
</el-table> />
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" </template>
v-bind="state.pagination"/> </el-table-column>
</div> <el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip>
<template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="200" v-if="data.menu">
<template #default="scope">
<el-button
text
type="primary"
icon="view"
v-if="!scope.row.$cellEdit"
@click="formDialogRef.openDialog('view', scope.row.id)"
v-auth="'order_handovernoderecord_view'"
>
{{ $t('common.viewBtn') }}
</el-button>
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'order_handovernoderecord_edit'"
v-if="!scope.row.$cellEdit"
@click="methods.handleEdit(scope.row, scope.$index, true)"
>{{ $t('common.editBtn') }}
</el-button>
<el-button
icon="Check"
text
type="primary"
v-auth="'order_handovernoderecord_add'"
v-if="scope.row.$cellEdit"
@click="methods.handleUpdate(scope.row, scope.$index)"
>{{ $t('jfI18n.saveBtn') }}
</el-button>
<el-button
icon="Close"
text
type="primary"
v-auth="'order_handovernoderecord_edit'"
v-if="scope.row.$cellEdit"
@click="methods.handleEdit(scope.row, scope.$index, false)"
>{{ $t('jfI18n.cancelBtn') }}
</el-button>
<el-button icon="delete" text type="primary" v-auth="'order_handovernoderecord_del'" @click="handleDelete(scope.row.id, scope.$index)"
>{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)"/> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div> </div>
</template> </template>
<script setup lang="ts" name="HandoverNodeRecordFlow"> <script setup lang="ts" name="HandoverNodeRecordFlow">
import {BasicTableProps, useTable} from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import * as handoverNodeRecord from '/@/api/order/handover-node-record' import * as handoverNodeRecord from '/@/api/order/handover-node-record';
import {useMessage, useMessageBox} from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onFormLoadedUrl, onLoadDicUrl, onLoaded, remoteMethodByKey} from "/@/flow/components/convert-name/convert"; import { onFormLoadedUrl, onLoadDicUrl, onLoaded, remoteMethodByKey } from '/@/flow/components/convert-name/convert';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import * as handoverFlow from '/@/api/order/handover-flow' import * as handoverFlow from '/@/api/order/handover-flow';
import * as orderVue from "/@/api/order/order-key-vue"; import * as orderVue from '/@/api/order/order-key-vue';
import {PROP_CONST} from '/@/flow/support/prop-const' import { PROP_CONST } from '/@/flow/support/prop-const';
import {useUserInfo} from "/@/stores/userInfo"; import { useUserInfo } from '/@/stores/userInfo';
import {setCrudQueryForm} from "/@/flow/support/common"; import { setCrudQueryForm } from '/@/flow/support/common';
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const {t} = useI18n() const { t } = useI18n();
const userInfo = useUserInfo(); const userInfo = useUserInfo();
// 定义查询字典 // 定义查询字典
const dicData = reactive({}); const dicData = reactive({});
const onLoad = onLoadDicUrl(); const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "receiveDept"}, {key: "receiveUser"}); const onFormLoaded = onFormLoadedUrl({ key: 'receiveDept' }, { key: 'receiveUser' });
onMounted(() => { onMounted(() => {
// onLoad(dicData); // onLoad(dicData);
}); });
function remoteMethodDept(query: string) { function remoteMethodDept(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'deptName', "receiveDept") remoteMethodByKey(query, onLoad, dicData, 'deptName', 'receiveDept');
} }
function remoteMethodUser(query: string) { function remoteMethodUser(query: string) {
remoteMethodByKey(query, onLoad, dicData, 'userName', "receiveUser") remoteMethodByKey(query, onLoad, dicData, 'userName', 'receiveUser');
} }
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const onTableLoaded = async (state) => { const onTableLoaded = async (state) => {
let onLoadedInit = onLoaded({key: "createUser"}, {key: "initiatorId"}, {key: "flowInstId"}, {key: "runJobId"}); let onLoadedInit = onLoaded({ key: 'createUser' }, { key: 'initiatorId' }, { key: 'flowInstId' }, { key: 'runJobId' });
await onLoadedInit(state) await onLoadedInit(state);
await onFormLoaded(dicData, state.queryForm) await onFormLoaded(dicData, state.queryForm);
} };
const validate = async () => { const validate = async () => {
// 防止被手动修改 // 防止被手动修改
methods.setCrudQueryForm() methods.setCrudQueryForm();
if (!state.queryForm.status || !state.queryForm.type) { if (!state.queryForm.status || !state.queryForm.type) {
useMessage().info('请选择交接条件') useMessage().info('请选择交接条件');
return false return false;
} }
// 被驳回时查询 // 被驳回时查询
state.queryForm.createUser = props.currJob.createUser state.queryForm.createUser = props.currJob.createUser;
return true return true;
} };
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: handoverNodeRecord.fetchFlowList, pageList: handoverNodeRecord.fetchFlowList,
validate: validate, validate: validate,
onLoaded: onTableLoaded, onLoaded: onTableLoaded,
descs: ["create_time"], descs: ['create_time'],
createdIsNeed: false createdIsNeed: false,
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
data.selections = [] data.selections = [];
getDataList() getDataList();
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
data.selections = objs data.selections = objs;
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (id: string, index) => { const handleDelete = async (id: string, index) => {
if (validateNull(id)) { if (validateNull(id)) {
useMessage().info('还未保存') useMessage().info('还未保存');
return return;
} }
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await handoverNodeRecord.delObjs(id); await handoverNodeRecord.delObjs(id);
state.dataList.splice(index, 1) state.dataList.splice(index, 1);
state.pagination!.total = state.dataList.length state.pagination!.total = state.dataList.length;
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
const props = defineProps({ const props = defineProps({
currJob: { currJob: {
type: Object, type: Object,
default: null, default: null,
}, },
currElTab: { currElTab: {
type: Object, type: Object,
default: {}, default: {},
}, },
}); });
const data = reactive({ const data = reactive({
selections: [], selections: [],
isCheckToReject: false, isCheckToReject: false,
isConfirmReceive: false, isConfirmReceive: false,
menu: false, menu: false,
selection: false selection: false,
}) });
const methods = { const methods = {
initJobData() { initJobData() {
state.loading = false state.loading = false;
data.menu = false; data.menu = false;
data.selection = false; data.selection = false;
data.isConfirmReceive = false data.isConfirmReceive = false;
data.isCheckToReject = false data.isCheckToReject = false;
methods.initSelections() methods.initSelections();
}, },
async resolveMethod() { async resolveMethod() {
let index = state.dataList.findIndex(f => f.retStatus === '1') + 1 let index = state.dataList.findIndex((f) => f.retStatus === '1') + 1;
if (index !== 0) { if (index !== 0) {
useMessage().info('第 ' + index + ' 行交接项未修改,请先修改并保存') useMessage().info('第 ' + index + ' 行交接项未修改,请先修改并保存');
return false return false;
} }
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true);
return true return true;
}, },
async initSelections() { async initSelections() {
let hiJob = props.currJob.hiJob let hiJob = props.currJob.hiJob;
// 判断是否仅查看 // 判断是否仅查看
await orderVue.currElTabIsView(null, props.currJob, props.currElTab.id) await orderVue.currElTabIsView(null, props.currJob, props.currElTab.id);
let find = orderVue.currElTabIsExist(props.currJob, props.currElTab.id) let find = orderVue.currElTabIsExist(props.currJob, props.currElTab.id);
if (find.isFormEdit === '0') hiJob = true if (find.isFormEdit === '0') hiJob = true;
let userKey = props.currJob.userKey let userKey = props.currJob.userKey;
let currJob = PROP_CONST.HANDOVER_FLOW.userKey let currJob = PROP_CONST.HANDOVER_FLOW.userKey;
if (userKey === currJob.create_user) { if (userKey === currJob.create_user) {
// 判断修改工单 // 判断修改工单
if (!hiJob) {// 为0后带入参数查询不出1的数据 if (!hiJob) {
state.queryForm.retStatus = '1' // 为0后带入参数查询不出1的数据
data.menu = true; state.queryForm.retStatus = '1';
} else data.menu = false; data.menu = true;
// 被驳回需全部保存 } else data.menu = false;
props.currJob.resolveMethods.push(methods.resolveMethod) // 被驳回需全部保存
} else if (userKey === currJob.receive_user || userKey === currJob.curr_dept_manager) { props.currJob.resolveMethods.push(methods.resolveMethod);
data.menu = false; } else if (userKey === currJob.receive_user || userKey === currJob.curr_dept_manager) {
state.queryForm.retStatus = '0' data.menu = false;
// 判断驳回工单 state.queryForm.retStatus = '0';
if (!hiJob) { // 判断驳回工单
data.isCheckToReject = true if (!hiJob) {
data.selection = true; data.isCheckToReject = true;
if (userKey === currJob.curr_dept_manager) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true) data.selection = true;
} if (userKey === currJob.curr_dept_manager) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true);
// 判断接收工单 }
if (userKey === currJob.receive_user && !hiJob) { // 判断接收工单
props.currJob.resolveSaves.push(methods.handleConfirmReceive) if (userKey === currJob.receive_user && !hiJob) {
data.isConfirmReceive = true props.currJob.resolveSaves.push(methods.handleConfirmReceive);
} data.isConfirmReceive = true;
} else { }
// 其他节点自动完成 } else {
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true) // 其他节点自动完成
} orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true);
// 初始化 }
getDataList() // 初始化
}, getDataList();
async handleUpdate(row, index) { },
try { async handleUpdate(row, index) {
state.loading = true; try {
// 防止被手动修改 state.loading = true;
methods.setCrudQueryForm() // 防止被手动修改
// 驳回后修改为正常状态 methods.setCrudQueryForm();
if (state.queryForm.retStatus === '1') row.retStatus = '0' // 驳回后修改为正常状态
let resp = await handoverNodeRecord.putObj(Object.assign({}, state.queryForm, row)); if (state.queryForm.retStatus === '1') row.retStatus = '0';
// 延迟赋值 let resp = await handoverNodeRecord.putObj(Object.assign({}, state.queryForm, row));
setTimeout(() => { // 延迟赋值
state.dataList[index].id = resp.data.id setTimeout(() => {
state.dataList[index].$cellEdit = false state.dataList[index].id = resp.data.id;
}, 0) state.dataList[index].$cellEdit = false;
useMessage().success('操作成功') }, 0);
} catch (err: any) { useMessage().success('操作成功');
useMessage().error(err.msg); } catch (err: any) {
} finally { useMessage().error(err.msg);
state.loading = false; } finally {
} state.loading = false;
}, }
handleUpdateFlow() { },
// 可手动修改的交接参数 handleUpdateFlow() {
// common.setPropsDataValue(order, state.queryForm, 'handoverReason', 'receiveDept') // 可手动修改的交接参数
if (!state.queryForm.status || !state.queryForm.type || !state.queryForm.handoverReason || !state.queryForm.receiveDept) { // common.setPropsDataValue(order, state.queryForm, 'handoverReason', 'receiveDept')
useMessage().info('请选择交接条件:交接状态、交接类型、交接原因、接受部门') if (!state.queryForm.status || !state.queryForm.type || !state.queryForm.handoverReason || !state.queryForm.receiveDept) {
return useMessage().info('请选择交接条件:交接状态、交接类型、交接原因、接受部门');
} return;
methods.timeoutLoading(3) }
let queryForm = { methods.timeoutLoading(3);
id: props.currJob.orderId, let queryForm = {
code: props.currJob.code, id: props.currJob.orderId,
flowKey: props.currJob.flowKey, code: props.currJob.code,
flowInstId: props.currJob.flowInstId, flowKey: props.currJob.flowKey,
runJobId: props.currJob.id flowInstId: props.currJob.flowInstId,
} runJobId: props.currJob.id,
// 修改工单信息时,任务交接不允许修改类型、状态 };
let order = props.currJob.order // 修改工单信息时,任务交接不允许修改类型、状态
let assign = Object.assign({}, state.queryForm, queryForm, {type: order.type, status: order.status}); let order = props.currJob.order;
handoverFlow.putObj(assign).then(data => { let assign = Object.assign({}, state.queryForm, queryForm, { type: order.type, status: order.status });
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true) handoverFlow.putObj(assign).then((data) => {
useMessage().success('修改成功') orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true);
}) useMessage().success('修改成功');
}, });
// 防止被手动修改 },
setCrudQueryForm() { // 防止被手动修改
let order = props.currJob.order setCrudQueryForm() {
setCrudQueryForm(state, {prop: 'receiveDept', value: order.receiveDept}, {prop: 'type', value: order.type} let order = props.currJob.order;
, {prop: 'status', value: order.status}, {prop: 'receiveUser', value: order.receiveUser}, setCrudQueryForm(
{prop: 'handoverReason', value: order.handoverReason},{prop: 'orderId', value: props.currJob.orderId} ) state,
}, { prop: 'receiveDept', value: order.receiveDept },
handleCheckToReject() { { prop: 'type', value: order.type },
// 防止被手动修改 { prop: 'status', value: order.status },
methods.setCrudQueryForm() { prop: 'receiveUser', value: order.receiveUser },
// 驳回 { prop: 'handoverReason', value: order.handoverReason },
if (validateNull(data.selections)) { { prop: 'orderId', value: props.currJob.orderId }
useMessage().info('请选择需驳回的交接项') );
return },
} handleCheckToReject() {
methods.timeoutLoading(3) // 防止被手动修改
state.queryForm.id = data.selections[0].orderId methods.setCrudQueryForm();
state.queryForm.rejectIds = data.selections.map(m => m.id) // 驳回
handoverFlow.checkToReject(state.queryForm).then(data => { if (validateNull(data.selections)) {
state.queryForm.id = null useMessage().info('请选择需驳回的交接项');
getDataList() return;
useMessage().success('驳回成功') }
}) methods.timeoutLoading(3);
}, state.queryForm.id = data.selections[0].orderId;
async handleConfirmReceive() { state.queryForm.rejectIds = data.selections.map((m) => m.id);
// 防止被手动修改 handoverFlow.checkToReject(state.queryForm).then((data) => {
methods.setCrudQueryForm() state.queryForm.id = null;
// 接收 getDataList();
if (validateNull(state.dataList)) { useMessage().success('驳回成功');
useMessage().info('请选择交接项') });
return },
} async handleConfirmReceive() {
methods.timeoutLoading(3) // 防止被手动修改
state.queryForm.id = state.dataList[0].orderId methods.setCrudQueryForm();
await handoverFlow.confirmReceive(state.queryForm) // 接收
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, null) if (validateNull(state.dataList)) {
useMessage().success('接收成功') useMessage().info('请选择交接项');
}, return;
timeoutLoading(t) { }
state.loading = true methods.timeoutLoading(3);
setTimeout(() => {// 防重复提交 state.queryForm.id = state.dataList[0].orderId;
state.loading = false await handoverFlow.confirmReceive(state.queryForm);
}, t * 1000) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, null);
}, useMessage().success('接收成功');
handleEdit(row, index, bool) { },
row.$cellEdit = bool timeoutLoading(t) {
} state.loading = true;
} setTimeout(() => {
// 防重复提交
state.loading = false;
}, t * 1000);
},
handleEdit(row, index, bool) {
row.$cellEdit = bool;
},
};
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currJob.id, () => props.currJob.id,
() => { () => {
methods.initJobData(); methods.initJobData();
} }
); );
onMounted(() => { onMounted(() => {
methods.initJobData() methods.initJobData();
}); });
</script> </script>

View File

@@ -1,166 +1,175 @@
<template> <template>
<el-dialog :title="title" v-model="visible" width="60%" <el-dialog :title="title" v-model="visible" width="60%" :close-on-click-modal="false" draggable>
:close-on-click-modal="false" draggable> <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading" :disabled="operType === 'view'"> <el-row :gutter="24">
<el-row :gutter="24"> <el-col :span="12" class="mb20">
<el-col :span="12" class="mb20"> <el-form-item :label="t('handoverNodeRecord.flowKey')" prop="flowKey">
<el-form-item :label="t('handoverNodeRecord.flowKey')" prop="flowKey"> <el-select v-model="form.flowKey" :placeholder="t('handoverNodeRecord.inputFlowKeyTip')" clearable filterable disabled>
<el-select v-model="form.flowKey" :placeholder="t('handoverNodeRecord.inputFlowKeyTip')" clearable filterable disabled> <el-option v-for="(item, index) in cascadeDic.flowKey" :key="index" :label="item.flowName" :value="item.flowKey"></el-option>
<el-option v-for="(item, index) in cascadeDic.flowKey" :key="index" :label="item.flowName" :value="item.flowKey"></el-option> </el-select>
</el-select> </el-form-item>
</el-form-item> </el-col>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverNodeRecord.initiatorId')" prop="initiatorId"> <el-form-item :label="t('handoverNodeRecord.initiatorId')" prop="initiatorId">
<el-select v-model="form.initiatorId" :placeholder="t('handoverNodeRecord.inputInitiatorIdTip')" clearable filterable disabled> <el-select v-model="form.initiatorId" :placeholder="t('handoverNodeRecord.inputInitiatorIdTip')" clearable filterable disabled>
<el-option v-for="(item, index) in dicData.initiatorId" :key="index" :label="item.name" :value="item.userId"></el-option> <el-option v-for="(item, index) in dicData.initiatorId" :key="index" :label="item.name" :value="item.userId"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverNodeRecord.runJobId')" prop="runJobId"> <el-form-item :label="t('handoverNodeRecord.runJobId')" prop="runJobId">
<el-select v-model="form.runJobId" :placeholder="t('handoverNodeRecord.inputRunJobIdTip')" clearable filterable disabled> <el-select v-model="form.runJobId" :placeholder="t('handoverNodeRecord.inputRunJobIdTip')" clearable filterable disabled>
<el-option v-for="(item, index) in cascadeDic.runJobId" :key="index" :label="item.jobName" :value="item.id"></el-option> <el-option v-for="(item, index) in cascadeDic.runJobId" :key="index" :label="item.jobName" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverNodeRecord.startTime')" prop="startTime"> <el-form-item :label="t('handoverNodeRecord.startTime')" prop="startTime">
<el-date-picker disabled type="datetime" :placeholder="t('handoverNodeRecord.inputStartTimeTip')" v-model="form.startTime" :value-format="dateTimeStr"></el-date-picker> <el-date-picker
</el-form-item> disabled
</el-col> type="datetime"
:placeholder="t('handoverNodeRecord.inputStartTimeTip')"
v-model="form.startTime"
:value-format="dateTimeStr"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverNodeRecord.todoList')" prop="todoList"> <el-form-item :label="t('handoverNodeRecord.todoList')" prop="todoList">
<el-input v-model="form.todoList" type="textarea" :placeholder="t('handoverNodeRecord.inputTodoListTip')"/> <el-input v-model="form.todoList" type="textarea" :placeholder="t('handoverNodeRecord.inputTodoListTip')" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="12" class="mb20">
<el-form-item :label="t('handoverNodeRecord.status')" prop="status"> <el-form-item :label="t('handoverNodeRecord.status')" prop="status">
<el-select v-model="form.status" :placeholder="t('handoverNodeRecord.inputStatusTip')" clearable filterable disabled> <el-select v-model="form.status" :placeholder="t('handoverNodeRecord.inputStatusTip')" clearable filterable disabled>
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
</el-row> </el-form>
</el-form> <template #footer v-if="operType !== 'view'">
<template #footer v-if="operType !== 'view'"> <span class="dialog-footer">
<span class="dialog-footer"> <el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button> <el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button> </span>
</span> </template>
</template> </el-dialog>
</el-dialog>
</template> </template>
<script setup lang="ts" name="HandoverNodeRecordDialog"> <script setup lang="ts" name="HandoverNodeRecordDialog">
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/order/handover-node-record';
import { useI18n } from 'vue-i18n';
import { rule } from '/@/utils/validate';
import { onCascadeChange, onFormLoadedUrl, onLoadDicUrl } from '/@/flow/components/convert-name/convert';
const emit = defineEmits(['refresh']);
import { useMessage } from "/@/hooks/message"; const { t } = useI18n();
import { getObj, addObj, putObj } from '/@/api/order/handover-node-record'
import { useI18n } from "vue-i18n"
import { rule } from '/@/utils/validate';
import {onCascadeChange, onFormLoadedUrl, onLoadDicUrl} from "/@/flow/components/convert-name/convert";
const emit = defineEmits(['refresh']);
const { t } = useI18n(); // 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const operType = ref(false);
const title = ref('');
// 定义字典
const dicData = reactive({});
const cascadeDic = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({ key: 'initiatorId' });
const onCascade = onCascadeChange(
cascadeDic,
{ key: 'flowInstId', cascades: ['flowKey', 'runNodeId'] },
{ key: 'runNodeId', cascades: ['runJobId'] }
);
onMounted(() => {
// onLoad(dicData);
});
// 提交表单数据
const form = reactive({
flowKey: '',
initiatorId: '',
nodeJobId: '',
startTime: '',
todoList: '',
status: '',
});
// 定义变量内容 // 定义校验规则
const dataFormRef = ref(); const dataRules = ref({
const visible = ref(false); flowKey: [{ required: true, message: '业务类型不能为空', trigger: 'blur' }],
const loading = ref(false); initiatorId: [{ required: true, message: '交接申请人不能为空', trigger: 'blur' }],
const operType = ref(false); nodeJobId: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
const title = ref(''); status: [{ required: true, message: '交接状态不能为空', trigger: 'blur' }],
// 定义字典 });
const dicData = reactive({});
const cascadeDic = reactive({});
const onLoad = onLoadDicUrl();
const onFormLoaded = onFormLoadedUrl({key: "initiatorId"});
const onCascade = onCascadeChange(cascadeDic, {key: "flowInstId", cascades: ["flowKey", "runNodeId"]}, {key: "runNodeId", cascades: ["runJobId"]});
onMounted(() => {
// onLoad(dicData);
});
// 提交表单数据
const form = reactive({
flowKey: '',
initiatorId: '',
nodeJobId: '',
startTime: '',
todoList: '',
status: '',
});
// 定义校验规则 // 打开弹窗
const dataRules = ref({ const openDialog = (type: string, id: string) => {
flowKey: [{required: true, message: '业务类型不能为空', trigger: 'blur'}], visible.value = true;
initiatorId: [{required: true, message: '交接申请人不能为空', trigger: 'blur'}], operType.value = type;
nodeJobId: [{required: true, message: '任务名称不能为空', trigger: 'blur'}], form.id = '';
status: [{required: true, message: '交接状态不能为空', trigger: 'blur'}],
})
// 打开弹窗 if (type === 'add') {
const openDialog = (type: string, id: string) => { title.value = t('common.addBtn');
visible.value = true } else if (type === 'edit') {
operType.value = type; title.value = t('common.editBtn');
form.id = '' } else if (type === 'view') {
title.value = t('common.viewBtn');
}
if (type === 'add') { // 重置表单数据
title.value = t('common.addBtn'); nextTick(() => {
} else if (type === 'edit') { dataFormRef.value?.resetFields();
title.value = t('common.editBtn'); });
} else if (type === 'view') {
title.value = t('common.viewBtn');
}
// 重置表单数据 // 获取HandoverNodeRecord信息
nextTick(() => { if (id) {
dataFormRef.value?.resetFields(); form.id = id;
}); getHandoverNodeRecordData(id);
}
};
// 获取HandoverNodeRecord信息 // 提交
if (id) { const onSubmit = async () => {
form.id = id const valid = await dataFormRef.value.validate().catch(() => {});
getHandoverNodeRecordData(id) if (!valid) return false;
}
};
// 提交 try {
const onSubmit = async () => { loading.value = true;
const valid = await dataFormRef.value.validate().catch(() => {}); form.id ? await putObj(form) : await addObj(form);
if (!valid) return false; useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
try { // 初始化表单数据
loading.value = true; const getHandoverNodeRecordData = (id: string) => {
form.id ? await putObj(form) : await addObj(form); // 获取数据
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText')); loading.value = true;
visible.value = false; getObj(id)
emit('refresh'); .then((res: any) => {
} catch (err: any) { Object.assign(form, res.data);
useMessage().error(err.msg); onFormLoaded(dicData, form);
} finally { onCascade(form);
loading.value = false; })
} .finally(() => {
}; loading.value = false;
});
};
// 初始化表单数据 // 暴露变量
const getHandoverNodeRecordData = (id: string) => { defineExpose({
// 获取数据 openDialog,
loading.value = true });
getObj(id).then((res: any) => {
Object.assign(form, res.data)
onFormLoaded(dicData, form);
onCascade(form);
}).finally(() => {
loading.value = false
})
};
// 暴露变量
defineExpose({
openDialog
});
</script> </script>

View File

@@ -1,40 +1,39 @@
export default { export default {
handoverNodeRecord: { handoverNodeRecord: {
index: '#', index: '#',
importhandoverNodeRecordTip: 'import HandoverNodeRecord', importhandoverNodeRecordTip: 'import HandoverNodeRecord',
id: 'id', id: 'id',
flowKey: 'flowKey', flowKey: 'flowKey',
initiatorId: 'initiatorId', initiatorId: 'initiatorId',
nodeJobId: 'nodeJobId', nodeJobId: 'nodeJobId',
startTime: 'startTime', startTime: 'startTime',
todoList: 'todoList', todoList: 'todoList',
status: 'status', status: 'status',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
orderId: 'orderId', orderId: 'orderId',
flowInstId: 'flowInstId', flowInstId: 'flowInstId',
retStatus: 'retStatus', retStatus: 'retStatus',
runJobId: 'runJobId', runJobId: 'runJobId',
runNodeId: 'runNodeId', runNodeId: 'runNodeId',
updateUser: 'updateUser', updateUser: 'updateUser',
updateTime: 'updateTime', updateTime: 'updateTime',
inputIdTip: 'input id', inputIdTip: 'input id',
inputFlowKeyTip: 'input flowKey', inputFlowKeyTip: 'input flowKey',
inputInitiatorIdTip: 'input initiatorId', inputInitiatorIdTip: 'input initiatorId',
inputNodeJobIdTip: 'input nodeJobId', inputNodeJobIdTip: 'input nodeJobId',
inputStartTimeTip: 'input startTime', inputStartTimeTip: 'input startTime',
inputTodoListTip: 'input todoList', inputTodoListTip: 'input todoList',
inputStatusTip: 'input status', inputStatusTip: 'input status',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputOrderIdTip: 'input orderId', inputOrderIdTip: 'input orderId',
inputFlowInstIdTip: 'input flowInstId', inputFlowInstIdTip: 'input flowInstId',
inputRetStatusTip: 'input retStatus', inputRetStatusTip: 'input retStatus',
inputRunJobIdTip: 'input runJobId', inputRunJobIdTip: 'input runJobId',
inputRunNodeIdTip: 'input runNodeId', inputRunNodeIdTip: 'input runNodeId',
inputUpdateUserTip: 'input updateUser', inputUpdateUserTip: 'input updateUser',
inputUpdateTimeTip: 'input updateTime', inputUpdateTimeTip: 'input updateTime',
},
} };
}

View File

@@ -1,40 +1,39 @@
export default { export default {
handoverNodeRecord: { handoverNodeRecord: {
index: '#', index: '#',
importhandoverNodeRecordTip: '导入交接任务记录', importhandoverNodeRecordTip: '导入交接任务记录',
id: 'ID', id: 'ID',
flowKey: '流程名称', flowKey: '流程名称',
initiatorId: '交接申请人', initiatorId: '交接申请人',
nodeJobId: '任务名称', nodeJobId: '任务名称',
startTime: '任务开始时间', startTime: '任务开始时间',
todoList: '待办事项', todoList: '待办事项',
status: '交接状态', status: '交接状态',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
orderId: '交接流程表ID', orderId: '交接流程表ID',
flowInstId: '流程实例ID', flowInstId: '流程实例ID',
retStatus: '是否被驳回', retStatus: '是否被驳回',
runJobId: '任务名称', runJobId: '任务名称',
runNodeId: '运行节点ID', runNodeId: '运行节点ID',
updateUser: '修改人', updateUser: '修改人',
updateTime: '修改时间', updateTime: '修改时间',
inputIdTip: '请输入ID', inputIdTip: '请输入ID',
inputFlowKeyTip: '请输入业务类型', inputFlowKeyTip: '请输入业务类型',
inputInitiatorIdTip: '请输入交接申请人', inputInitiatorIdTip: '请输入交接申请人',
inputNodeJobIdTip: '请输入任务名称', inputNodeJobIdTip: '请输入任务名称',
inputStartTimeTip: '请输入任务开始时间', inputStartTimeTip: '请输入任务开始时间',
inputTodoListTip: '请输入待办事项', inputTodoListTip: '请输入待办事项',
inputStatusTip: '请输入交接状态', inputStatusTip: '请输入交接状态',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputOrderIdTip: '请输入交接流程表ID', inputOrderIdTip: '请输入交接流程表ID',
inputFlowInstIdTip: '请输入流程实例ID', inputFlowInstIdTip: '请输入流程实例ID',
inputRetStatusTip: '请输入是否被驳回', inputRetStatusTip: '请输入是否被驳回',
inputRunJobIdTip: '请输入任务名称', inputRunJobIdTip: '请输入任务名称',
inputRunNodeIdTip: '请输入运行节点ID', inputRunNodeIdTip: '请输入运行节点ID',
inputUpdateUserTip: '请输入修改人', inputUpdateUserTip: '请输入修改人',
inputUpdateTimeTip: '请输入修改时间', inputUpdateTimeTip: '请输入修改时间',
},
} };
}

View File

@@ -1,192 +1,205 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('handoverNodeRecord.flowInstId')" prop="flowInstId" > <el-form-item :label="$t('handoverNodeRecord.flowInstId')" prop="flowInstId">
<el-input :placeholder="t('handoverNodeRecord.inputFlowInstIdTip')" v-model="state.queryForm.flowInstId" clearable <el-input
style="max-width: 180px" /> :placeholder="t('handoverNodeRecord.inputFlowInstIdTip')"
</el-form-item> v-model="state.queryForm.flowInstId"
<el-form-item :label="$t('runFlow.flowKey')" prop="flowKey" > clearable
<el-input :placeholder="t('runFlow.inputFlowKeyTip')" v-model="state.queryForm.flowKey" clearable style="max-width: 180px"
style="max-width: 180px" /> />
</el-form-item> </el-form-item>
<el-form-item :label="$t('handoverNodeRecord.status')" prop="status" > <el-form-item :label="$t('runFlow.flowKey')" prop="flowKey">
<el-select v-model="state.queryForm.status" :placeholder="t('handoverNodeRecord.inputStatusTip')" clearable filterable style="max-width: 180px"> <el-input :placeholder="t('runFlow.inputFlowKeyTip')" v-model="state.queryForm.flowKey" clearable style="max-width: 180px" />
<el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> </el-form-item>
</el-select> <el-form-item :label="$t('handoverNodeRecord.status')" prop="status">
</el-form-item> <el-select
<el-form-item> v-model="state.queryForm.status"
<el-button icon="search" type="primary" @click="getDataList"> :placeholder="t('handoverNodeRecord.inputStatusTip')"
{{ $t('common.queryBtn') }} clearable
</el-button> filterable
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button> style="max-width: 180px"
</el-form-item> >
</el-form> <el-option v-for="(item, index) in DIC_PROP.HANDOVER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
</el-row> </el-select>
<el-row> </el-form-item>
<div class="mb8" style="width: 100%"> <el-form-item>
<el-tooltip placement="top"> <el-button icon="search" type="primary" @click="getDataList">
<template #content> {{ $t('common.queryBtn') }}
{{ $t('common.delBtn') }} </el-button>
</template> <el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" </el-form-item>
v-auth="'order_handovernoderecord_del'" @click="handleDelete(selectObjs)"> </el-form>
</el-button> </el-row>
</el-tooltip> <el-row>
<div class="mb8" style="width: 100%">
<el-tooltip placement="top">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button
plain
:disabled="multiple"
icon="Delete"
type="primary"
class="ml10"
v-auth="'order_handovernoderecord_del'"
@click="handleDelete(selectObjs)"
>
</el-button>
</el-tooltip>
<right-toolbar v-model:showSearch="showSearch" :export="'order_handovernoderecord_export'" <right-toolbar
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" v-model:showSearch="showSearch"
@queryTable="getDataList"></right-toolbar> :export="'order_handovernoderecord_export'"
</div> @exportExcel="exportExcel"
</el-row> class="ml10"
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" style="float: right; margin-right: 20px"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> @queryTable="getDataList"
<el-table-column type="selection" width="40" align="center" /> ></right-toolbar>
<el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40" /> </div>
<el-table-column prop="flowInstId" :label="t('runReject.flowInstId')" show-overflow-tooltip/> </el-row>
<el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip> <el-table
<template #default="scope"> :data="state.dataList"
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" v-loading="state.loading"
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name> style="width: 100%"
</template> @selection-change="handleSelectionChange"
</el-table-column> @sort-change="sortChangeHandle"
<el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip> >
<template #default="scope"> <el-table-column type="selection" width="40" align="center" />
<convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" <el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40" />
:valueKey="'userId'" :showKey="'name'"></convert-name> <el-table-column prop="flowInstId" :label="t('runReject.flowInstId')" show-overflow-tooltip />
</template> <el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip>
</el-table-column> <template #default="scope">
<el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip> <convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
<template #default="scope"> </template>
<convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" </el-table-column>
:valueKey="'id'" :showKey="'jobName'"></convert-name> <el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip>
</template> <template #default="scope">
</el-table-column> <convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" :valueKey="'userId'" :showKey="'name'"></convert-name>
<el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip/> </template>
<el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip/> </el-table-column>
<el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip> <el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag> <convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" :valueKey="'id'" :showKey="'jobName'"></convert-name>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="retStatus" :label="t('handoverNodeRecord.retStatus')" show-overflow-tooltip> <el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip />
<template #default="scope"> <el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip />
<dict-tag :options="DIC_PROP.YES_OR_NO" :value="scope.row.retStatus"></dict-tag> <el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip>
</template> <template #default="scope">
</el-table-column> <dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
<el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip> </template>
<template #default="scope"> </el-table-column>
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" <el-table-column prop="retStatus" :label="t('handoverNodeRecord.retStatus')" show-overflow-tooltip>
:valueKey="'userId'" :showKey="'name'"></convert-name> <template #default="scope">
</template> <dict-tag :options="DIC_PROP.YES_OR_NO" :value="scope.row.retStatus"></dict-tag>
</el-table-column> </template>
<el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip/> </el-table-column>
<el-table-column :label="$t('common.action')" width="100"> <el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<el-tooltip placement="top"> <convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
<template #content> </template>
{{ $t('common.viewBtn') }} </el-table-column>
</template> <el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip />
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)"> <el-table-column :label="$t('common.action')" width="100">
</el-button> <template #default="scope">
</el-tooltip> <el-tooltip placement="top">
<el-tooltip placement="top"> <template #content>
<template #content> {{ $t('common.viewBtn') }}
{{ $t('common.editBtn') }} </template>
</template> <el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)"> </el-button>
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)"> </el-tooltip>
</el-button> <el-tooltip placement="top">
</el-tooltip> <template #content>
<el-tooltip placement="top"> {{ $t('common.editBtn') }}
<template #content> </template>
{{ $t('common.delBtn') }} <el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog('edit', scope.row.id)"> </el-button>
</template> </el-tooltip>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> <el-tooltip placement="top">
</el-button> <template #content>
</el-tooltip> {{ $t('common.delBtn') }}
</template> </template>
</el-table-column> <el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </el-button>
</el-table> </el-tooltip>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" /> </template>
</div> </el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" /> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div> </div>
</template> </template>
<script setup lang="ts" name="systemHandoverNodeRecord"> <script setup lang="ts" name="systemHandoverNodeRecord">
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from "/@/api/order/handover-node-record"; import { fetchList, delObjs } from '/@/api/order/handover-node-record';
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onLoaded} from "/@/flow/components/convert-name/convert"; import { onLoaded } from '/@/flow/components/convert-name/convert';
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { t } = useI18n() const { t } = useI18n();
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: fetchList, pageList: fetchList,
onLoaded: onLoaded({key: "createUser"}, {key: "initiatorId"}, {key: "flowInstId"}, {key: "runJobId"}), onLoaded: onLoaded({ key: 'createUser' }, { key: 'initiatorId' }, { key: 'flowInstId' }, { key: 'runJobId' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/handover-node-record/export', state.queryForm, 'handover-node-record.xlsx') downBlobFile('/order/handover-node-record/export', state.queryForm, 'handover-node-record.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await delObjs(ids); await delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
</script> </script>

View File

@@ -1,254 +1,286 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row> <el-row>
<div class="mb8" style="width: 100%"> <div class="mb8" style="width: 100%">
<el-button icon="Promotion" type="primary" class="ml10" @click="methods.handleInitiate" <el-button
:loading="state.loading" v-if="!validateNull(props.selections)" icon="Promotion"
v-auth="'order_handovernoderecord_add'"> type="primary"
{{ $t('jfI18n.initialBtn') }} class="ml10"
</el-button> @click="methods.handleInitiate"
<el-button icon="Check" type="primary" class="ml10" @click="methods.batchSaveOrUpdate" :loading="state.loading"
:loading="state.loading" v-if="!validateNull(props.selections)" v-if="!validateNull(props.selections)"
v-auth="'order_handovernoderecord_add'"> v-auth="'order_handovernoderecord_add'"
{{ $t('jfI18n.batchBtn') }} >
</el-button> {{ $t('jfI18n.initialBtn') }}
</div> </el-button>
</el-row> <el-button
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" icon="Check"
@sort-change="sortChangeHandle"> type="primary"
<el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40"/> class="ml10"
<el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip> @click="methods.batchSaveOrUpdate"
<template #default="scope"> :loading="state.loading"
<convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" v-if="!validateNull(props.selections)"
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name> v-auth="'order_handovernoderecord_add'"
</template> >
</el-table-column> {{ $t('jfI18n.batchBtn') }}
<el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip> </el-button>
<template #default="scope"> </div>
<convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" </el-row>
:valueKey="'userId'" :showKey="'name'"></convert-name> <el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" @sort-change="sortChangeHandle">
</template> <el-table-column type="index" :label="t('handoverNodeRecord.index')" width="40" />
</el-table-column> <el-table-column prop="flowKey" :label="t('handoverNodeRecord.flowKey')" show-overflow-tooltip>
<el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip> <template #default="scope">
<template #default="scope"> <convert-name :options="state.dicData.flowInstId" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
<convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" </template>
:valueKey="'id'" :showKey="'jobName'"></convert-name> </el-table-column>
</template> <el-table-column prop="initiatorId" :label="t('handoverNodeRecord.initiatorId')" show-overflow-tooltip>
</el-table-column> <template #default="scope">
<el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip/> <convert-name :options="state.dicData.initiatorId" :value="scope.row.initiatorId" :valueKey="'userId'" :showKey="'name'"></convert-name>
<el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip> </template>
<template #default="scope"> </el-table-column>
<el-input :disabled="!scope.row.$cellEdit" v-model="scope.row.todoList" type="textarea" :placeholder="t('handoverNodeRecord.inputTodoListTip')"/> <el-table-column prop="runJobId" :label="t('handoverNodeRecord.runJobId')" show-overflow-tooltip>
</template> <template #default="scope">
</el-table-column> <convert-name :options="state.dicData.runJobId" :value="scope.row.runJobId" :valueKey="'id'" :showKey="'jobName'"></convert-name>
<el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip> </template>
<template #default="scope"> </el-table-column>
<dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag> <el-table-column prop="startTime" :label="t('handoverNodeRecord.startTime')" show-overflow-tooltip />
</template> <el-table-column prop="todoList" :label="t('handoverNodeRecord.todoList')" show-overflow-tooltip>
</el-table-column> <template #default="scope">
<el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip> <el-input
<template #default="scope"> :disabled="!scope.row.$cellEdit"
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" v-model="scope.row.todoList"
:valueKey="'userId'" :showKey="'name'"></convert-name> type="textarea"
</template> :placeholder="t('handoverNodeRecord.inputTodoListTip')"
</el-table-column> />
<el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip/> </template>
<el-table-column :label="$t('common.action')" width="200"> </el-table-column>
<template #default="scope"> <el-table-column prop="status" :label="t('handoverNodeRecord.status')" show-overflow-tooltip>
<el-button text type="primary" icon="view" v-if="!scope.row.$cellEdit && scope.row.id" <template #default="scope">
@click="formDialogRef.openDialog('view', scope.row.id)" v-auth="'order_handovernoderecord_edit'"> <dict-tag :options="DIC_PROP.HANDOVER_STATUS" :value="scope.row.status"></dict-tag>
{{ $t('common.viewBtn') }} </template>
</el-button> </el-table-column>
<el-button icon="edit-pen" text type="primary" v-auth="'order_handovernoderecord_edit'" v-if="!scope.row.$cellEdit" <el-table-column prop="createUser" :label="t('handoverNodeRecord.createUser')" show-overflow-tooltip>
@click="methods.handleEdit(scope.row, scope.$index, true)">{{ $t('common.editBtn') }} <template #default="scope">
</el-button> <convert-name :options="state.dicData.createUser" :value="scope.row.createUser" :valueKey="'userId'" :showKey="'name'"></convert-name>
<el-button icon="Check" text type="primary" v-auth="'order_handovernoderecord_add'" v-if="scope.row.$cellEdit" </template>
@click="methods.handleUpdate(scope.row, scope.$index)">{{ $t('jfI18n.saveBtn') }} </el-table-column>
</el-button> <el-table-column prop="createTime" :label="t('handoverNodeRecord.createTime')" show-overflow-tooltip />
<el-button icon="Close" text type="primary" v-auth="'order_handovernoderecord_edit'" v-if="scope.row.$cellEdit && scope.row.id" <el-table-column :label="$t('common.action')" width="200">
@click="methods.handleEdit(scope.row, scope.$index, false)">{{ $t('jfI18n.cancelBtn') }} <template #default="scope">
</el-button> <el-button
<el-button icon="delete" text type="primary" v-auth="'order_handovernoderecord_del'" text
@click="handleDelete(scope.row.id, scope.$index)">{{ type="primary"
$t('common.delBtn') icon="view"
}} v-if="!scope.row.$cellEdit && scope.row.id"
</el-button> @click="formDialogRef.openDialog('view', scope.row.id)"
</template> v-auth="'order_handovernoderecord_edit'"
</el-table-column> >
</el-table> {{ $t('common.viewBtn') }}
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" </el-button>
v-bind="state.pagination"/> <el-button
</div> icon="edit-pen"
text
type="primary"
v-auth="'order_handovernoderecord_edit'"
v-if="!scope.row.$cellEdit"
@click="methods.handleEdit(scope.row, scope.$index, true)"
>{{ $t('common.editBtn') }}
</el-button>
<el-button
icon="Check"
text
type="primary"
v-auth="'order_handovernoderecord_add'"
v-if="scope.row.$cellEdit"
@click="methods.handleUpdate(scope.row, scope.$index)"
>{{ $t('jfI18n.saveBtn') }}
</el-button>
<el-button
icon="Close"
text
type="primary"
v-auth="'order_handovernoderecord_edit'"
v-if="scope.row.$cellEdit && scope.row.id"
@click="methods.handleEdit(scope.row, scope.$index, false)"
>{{ $t('jfI18n.cancelBtn') }}
</el-button>
<el-button icon="delete" text type="primary" v-auth="'order_handovernoderecord_del'" @click="handleDelete(scope.row.id, scope.$index)"
>{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 --> <!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)"/> <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div> </div>
</template> </template>
<script setup lang="ts" name="HandoverNodeRecordInitiate"> <script setup lang="ts" name="HandoverNodeRecordInitiate">
import {BasicTableProps, useTable} from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import * as handoverNodeRecord from '/@/api/order/handover-node-record' import * as handoverNodeRecord from '/@/api/order/handover-node-record';
import {useMessage, useMessageBox} from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onLoaded} from "/@/flow/components/convert-name/convert"; import { onLoaded } from '/@/flow/components/convert-name/convert';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import * as handoverFlow from '/@/api/order/handover-flow' import * as handoverFlow from '/@/api/order/handover-flow';
import {deepClone} from "/@/utils/other"; import { deepClone } from '/@/utils/other';
import {paramsFilter} from "/@/flow"; import { paramsFilter } from '/@/flow';
const $emit = defineEmits(['onHandoverFlow']); const $emit = defineEmits(['onHandoverFlow']);
const props = defineProps({ const props = defineProps({
selections: { selections: {
type: Object, type: Object,
default: null, default: null,
}, },
handoverForm: { handoverForm: {
type: Object, type: Object,
default: null, default: null,
} },
}); });
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const {t} = useI18n() const { t } = useI18n();
// 定义变量内容 // 定义变量内容
const formDialogRef = ref() const formDialogRef = ref();
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
createdIsNeed: false, createdIsNeed: false,
isPage: false isPage: false,
}) });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
} = useTable(state)
// 删除操作 // 删除操作
const handleDelete = async (id: string, index) => { const handleDelete = async (id: string, index) => {
if (validateNull(id)) { if (validateNull(id)) {
useMessage().info('还未保存') useMessage().info('还未保存');
return return;
} }
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await handoverNodeRecord.delObj(id); await handoverNodeRecord.delObj(id);
state.dataList.splice(index, 1) state.dataList.splice(index, 1);
state.pagination!.total = state.dataList.length state.pagination!.total = state.dataList.length;
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
const initLoaded = onLoaded({key: "flowInstId"}, {key: "runJobId"}, {key: "createUser"}, {key: "initiatorId"}) const initLoaded = onLoaded({ key: 'flowInstId' }, { key: 'runJobId' }, { key: 'createUser' }, { key: 'initiatorId' });
const methods = { const methods = {
async initSelections() { async initSelections() {
state.loading = true state.loading = true;
// 搜索条件 // 搜索条件
state.queryForm = paramsFilter(Object.assign(state.queryForm, props.handoverForm)) state.queryForm = paramsFilter(Object.assign(state.queryForm, props.handoverForm));
// 任务交接交接数据一定不能为空 // 任务交接交接数据一定不能为空
if (validateNull(props.selections)) { if (validateNull(props.selections)) {
useMessage().info('请选择交接项') useMessage().info('请选择交接项');
state.loading = false state.loading = false;
return return;
} }
let selections = deepClone(props.selections) let selections = deepClone(props.selections);
for (let i = 0; i < selections.length; i++) { for (let i = 0; i < selections.length; i++) {
selections[i].$cellEdit = true selections[i].$cellEdit = true;
selections[i].id = null// 防止复用 selections[i].id = null; // 防止复用
} }
Object.assign(state.dataList, selections) Object.assign(state.dataList, selections);
await initLoaded(state); await initLoaded(state);
state.pagination!.total = state.dataList.length state.pagination!.total = state.dataList.length;
state.loading = false state.loading = false;
}, },
async handleUpdate(row, index) { async handleUpdate(row, index) {
try { try {
state.loading = true; state.loading = true;
let resp = await handoverNodeRecord.putObj(Object.assign(row, state.queryForm)); let resp = await handoverNodeRecord.putObj(Object.assign(row, state.queryForm));
// 延迟赋值 // 延迟赋值
setTimeout(() => { setTimeout(() => {
state.dataList[index].id = resp.data.id state.dataList[index].id = resp.data.id;
state.dataList[index].$cellEdit = false state.dataList[index].$cellEdit = false;
}, 0) }, 0);
useMessage().success('操作成功') useMessage().success('操作成功');
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} finally { } finally {
state.loading = false; state.loading = false;
} }
}, },
handleInitiate() { handleInitiate() {
// 发起 // 发起
if (validateNull(state.dataList)) { if (validateNull(state.dataList)) {
useMessage().info('请选择交接项') useMessage().info('请选择交接项');
return return;
} }
let index = state.dataList.findIndex(f => !f.id) + 1 let index = state.dataList.findIndex((f) => !f.id) + 1;
if (index !== 0) { if (index !== 0) {
useMessage().info('第 ' + index + ' 行交接项未保存,请先保存') useMessage().info('第 ' + index + ' 行交接项未保存,请先保存');
return return;
} }
methods.timeoutLoading() methods.timeoutLoading();
// 防止再次发起复用 // 防止再次发起复用
let assign = Object.assign({}, state.queryForm, {id: state.dataList[0].orderId}); let assign = Object.assign({}, state.queryForm, { id: state.dataList[0].orderId });
handoverFlow.addObj(assign).then(data => { handoverFlow.addObj(assign).then((data) => {
useMessage().success('发起成功') useMessage().success('发起成功');
$emit('onHandoverFlow', state.queryForm.type) $emit('onHandoverFlow', state.queryForm.type);
}) });
}, },
batchSaveOrUpdate() { batchSaveOrUpdate() {
state.loading = true state.loading = true;
state.dataList[0] = Object.assign({}, state.queryForm, state.dataList[0]); state.dataList[0] = Object.assign({}, state.queryForm, state.dataList[0]);
handoverNodeRecord.batchSaveOrUpdate(state.dataList).then(resp => { handoverNodeRecord
// 延迟赋值 .batchSaveOrUpdate(state.dataList)
setTimeout(() => { .then((resp) => {
resp.data.forEach(data => { // 延迟赋值
let find = state.dataList.find(f => f.flowInstId === data.flowInstId && f.runJobId === data.runJobId) setTimeout(() => {
find.id = data.id resp.data.forEach((data) => {
find.type = state.queryForm.type let find = state.dataList.find((f) => f.flowInstId === data.flowInstId && f.runJobId === data.runJobId);
find.$cellEdit = false find.id = data.id;
}) find.type = state.queryForm.type;
useMessage().success('保存成功') find.$cellEdit = false;
state.loading = false });
}, 0) useMessage().success('保存成功');
}).catch((e) => { state.loading = false;
state.loading= false }, 0);
}) })
}, .catch((e) => {
timeoutLoading() { state.loading = false;
state.loading = true });
setTimeout(() => {// 防重复提交 },
state.loading = false timeoutLoading() {
}, 3 * 1000) state.loading = true;
}, setTimeout(() => {
handleEdit(row, index, bool) { // 防重复提交
row.$cellEdit = bool state.loading = false;
} }, 3 * 1000);
} },
handleEdit(row, index, bool) {
row.$cellEdit = bool;
},
};
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.selections.length, () => props.selections.length,
() => { () => {
methods.initSelections(); methods.initSelections();
}, },
{deep: true} { deep: true }
); );
onMounted(() => { onMounted(() => {
methods.initSelections() methods.initSelections();
}); });
</script> </script>

View File

@@ -1,151 +1,150 @@
import {flowConfig} from "/@/flow/designer/config/flow-config"; import { flowConfig } from '/@/flow/designer/config/flow-config';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import {useMessage} from "/@/hooks/message"; import { useMessage } from '/@/hooks/message';
import {DIC_PROP} from "/@/flow/support/dict-prop"; import { DIC_PROP } from '/@/flow/support/dict-prop';
import * as runApplication from "/@/api/order/run-application"; import * as runApplication from '/@/api/order/run-application';
import {disabledAllFormFields} from "/@/flow/utils/form-perm"; import { disabledAllFormFields } from '/@/flow/utils/form-perm';
export function validateApp($route) { export function validateApp($route) {
let appdata = flowConfig.mobileConfig.mobilePrefix let appdata = flowConfig.mobileConfig.mobilePrefix;
return !validateNull($route.query[appdata]); return !validateNull($route.query[appdata]);
} }
export function initJobDataByApp($route, handleGetObj, rowEditInitData) { export function initJobDataByApp($route, handleGetObj, rowEditInitData) {
// 兼容移动端 // 兼容移动端
let appdata = flowConfig.mobileConfig.mobilePrefix let appdata = flowConfig.mobileConfig.mobilePrefix;
let app = !validateNull($route.query[appdata]); let app = !validateNull($route.query[appdata]);
if (app) { if (app) {
let split = $route.query[appdata].split('-'); let split = $route.query[appdata].split('-');
let query = {id: split[1]} let query = { id: split[1] };
handleGetObj(query.id) handleGetObj(query.id);
} else { } else {
if (rowEditInitData) rowEditInitData() if (rowEditInitData) rowEditInitData();
} }
} }
export async function doInitData(formCreateRef, saveInitData) { export async function doInitData(formCreateRef, saveInitData) {
let state = await formCreateRef.value.design.fApi.validate(); let state = await formCreateRef.value.design.fApi.validate();
let formData = formCreateRef.value.design.formData; let formData = formCreateRef.value.design.formData;
if (!state || validateNull(formData)) { if (!state || validateNull(formData)) {
useMessage().warning("请输入表单信息"); useMessage().warning('请输入表单信息');
return return;
} }
return saveInitData(formData); return saveInitData(formData);
} }
export async function doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t) { export async function doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t) {
try { try {
loading.value = true; loading.value = true;
let formJson = await doInitData(formCreateRef, saveInitData) let formJson = await doInitData(formCreateRef, saveInitData);
props.currFlowForm.status !== DIC_PROP.ORDER_STATUS[0].value ? await runApplication.addObj(formJson) : await runApplication.putObj(formJson); props.currFlowForm.status !== DIC_PROP.ORDER_STATUS[0].value ? await runApplication.addObj(formJson) : await runApplication.putObj(formJson);
// 兼容移动端 // 兼容移动端
let app = validateApp($route); let app = validateApp($route);
if (app) { if (app) {
useMessage().success("发起成功,请在【我的申请】查看"); useMessage().success('发起成功,请在【我的申请】查看');
formCreateRef.value.design.fApi.disabled(true) formCreateRef.value.design.fApi.disabled(true);
data.submitBtn = false data.submitBtn = false;
} else { } else {
useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText')); useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText'));
$emit('handleInitiateOrder', false) $emit('handleInitiateOrder', false);
} }
} catch (err: any) { } catch (err: any) {
console.log(err) console.log(err);
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
export function doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData) { export function doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData) {
try { try {
loading.value = true; loading.value = true;
let formData = formCreateRef.value.design.formData; let formData = formCreateRef.value.design.formData;
if (validateNull(formData)) { if (validateNull(formData)) {
useMessage().warning("请输入表单信息"); useMessage().warning('请输入表单信息');
return return;
} }
let formJson = saveInitData(formData); let formJson = saveInitData(formData);
runApplication.tempStore(formJson).then(resp => { runApplication.tempStore(formJson).then((resp) => {
useMessage().success("暂存成功,请在【我的申请】查看"); useMessage().success('暂存成功,请在【我的申请】查看');
// 兼容移动端 // 兼容移动端
let app = validateApp($route); let app = validateApp($route);
if (app) { if (app) {
formCreateRef.value.design.fApi.disabled(true) formCreateRef.value.design.fApi.disabled(true);
data.submitBtn = false data.submitBtn = false;
} else { } else {
$emit('handleInitiateOrder', false) $emit('handleInitiateOrder', false);
} }
}) });
} catch (err: any) { } catch (err: any) {
useMessage().error(err); useMessage().error(err);
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
function handleDisabledFields(disabledFields, disabled) { function handleDisabledFields(disabledFields, disabled) {
if (disabledFields) { if (disabledFields) {
Object.keys(disabledFields).forEach(key => { Object.keys(disabledFields).forEach((key) => {
disabledFields[key] = disabled disabledFields[key] = disabled;
}); });
} }
} }
export function initCustomFormMethods(data, disabledFields, operType?) { export function initCustomFormMethods(data, disabledFields, operType?) {
return { return {
disableForm(isAll) { disableForm(isAll) {
// 流程中页面因发起页面可copy // 流程中页面因发起页面可copy
if (isAll) operType.value = "view" if (isAll) operType.value = 'view';
else handleDisabledFields(disabledFields, true) else handleDisabledFields(disabledFields, true);
}, },
enableForm(isAll) { enableForm(isAll) {
if (isAll) operType.value = "flow" if (isAll) operType.value = 'flow';
else handleDisabledFields(disabledFields, false) else handleDisabledFields(disabledFields, false);
}, },
disableSubmit() { disableSubmit() {
data.submitBtn = false data.submitBtn = false;
}, },
enableSubmit() { enableSubmit() {
data.submitBtn = true data.submitBtn = true;
}, },
} };
} }
export function initFormMethods(formCreateRef, data) { export function initFormMethods(formCreateRef, data) {
return { return {
hiddenForm(isAll, widgetList) { hiddenForm(isAll, widgetList) {
if (isAll) { if (isAll) {
nextTick(async () => { nextTick(async () => {
formCreateRef.value.design.fApi.hidden(true) formCreateRef.value.design.fApi.hidden(true);
}) });
} else { } else {
disabledAllFormFields(widgetList, '-1') disabledAllFormFields(widgetList, '-1');
} }
}, },
disableForm(isAll, widgetList) { disableForm(isAll, widgetList) {
if (isAll) { if (isAll) {
// TODO 目前调用会早于规则赋值无效 // TODO 目前调用会早于规则赋值无效
nextTick(async () => { nextTick(async () => {
formCreateRef.value.design.fApi.disabled(true) formCreateRef.value.design.fApi.disabled(true);
}) });
} else { } else {
disabledAllFormFields(widgetList, '0') disabledAllFormFields(widgetList, '0');
} }
}, },
enableForm(isAll, widgetList) { enableForm(isAll, widgetList) {
if (isAll) { if (isAll) {
nextTick(async () => { nextTick(async () => {
formCreateRef.value.design.fApi.disabled(false) formCreateRef.value.design.fApi.disabled(false);
}) });
} else { } else {
disabledAllFormFields(widgetList, '1') disabledAllFormFields(widgetList, '1');
} }
}, },
disableSubmit() { disableSubmit() {
data.submitBtn = false data.submitBtn = false;
}, },
enableSubmit() { enableSubmit() {
data.submitBtn = true data.submitBtn = true;
}, },
} };
} }

View File

@@ -1,202 +1,196 @@
<template> <template>
<div> <div>
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<form-render ref="formCreateRef" :currFlowForm="data.currFlowFormClone" v-if="!validateNull(data.currFlowFormClone)" <form-render
:initFormPermPrint="initFormPermPrint"> ref="formCreateRef"
</form-render> :currFlowForm="data.currFlowFormClone"
v-if="!validateNull(data.currFlowFormClone)"
:initFormPermPrint="initFormPermPrint"
>
</form-render>
<footer class="el-dialog__footer" v-if="data.submitBtn"> <footer class="el-dialog__footer" v-if="data.submitBtn">
<span class="dialog-footer"> <span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{ <el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{ t('jfI18n.print') }} </el-button>
t('jfI18n.print') <el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }} </el-button>
}} </span>
</el-button> </footer>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ <footer class="el-dialog__footer" v-else>
t('jfI18n.submit') <span class="dialog-footer">
}} <el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{ t('jfI18n.print') }} </el-button>
</el-button> </span>
</span> </footer>
</footer> </div>
<footer class="el-dialog__footer" v-else>
<span class="dialog-footer">
<el-button type="primary" @click="printForm" v-if="data.currFlowForm.printInfo">{{
t('jfI18n.print')
}}
</el-button>
</span>
</footer>
</div>
<!-- 打印表单 --> <!-- 打印表单 -->
<el-dialog v-model="data.showTinymceView" top="20px" width="700px" <el-dialog v-model="data.showTinymceView" top="20px" width="700px" :title="data.tinymceTitle" append-to-body @close="closePrint">
:title="data.tinymceTitle" append-to-body <tinymce-view v-if="data.showTinymceView" :currFlowForm="data.currFlowForm"></tinymce-view>
@close="closePrint"> </el-dialog>
<tinymce-view v-if="data.showTinymceView" :currFlowForm="data.currFlowForm"></tinymce-view> </div>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="RunApplicationForm"> <script setup lang="ts" name="RunApplicationForm">
import * as runApplication from '/@/api/order/run-application' import * as runApplication from '/@/api/order/run-application';
import * as orderVue from "/@/api/order/order-key-vue"; import * as orderVue from '/@/api/order/order-key-vue';
import {parseWithFunctions} from "/@/flow"; import { parseWithFunctions } from '/@/flow';
import {handleDesignFormPerm, handleFormPrint} from "/@/flow/utils/form-perm"; import { handleDesignFormPerm, handleFormPrint } from '/@/flow/utils/form-perm';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {useMessage} from "/@/hooks/message"; import { useMessage } from '/@/hooks/message';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import {deepClone} from "/@/utils/other"; import { deepClone } from '/@/utils/other';
import {initJobDataByApp} from "/@/flow/support/extend"; import { initJobDataByApp } from '/@/flow/support/extend';
import {initFormMethods} from "../index"; import { initFormMethods } from '../index';
const emits = defineEmits(["handleJob"]); const emits = defineEmits(['handleJob']);
const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue')); const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue'));
const formCreateRef = ref(null) const formCreateRef = ref(null);
// 引入组件 // 引入组件
const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue')); const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue'));
const {t} = useI18n(); const { t } = useI18n();
const loading = ref(false); const loading = ref(false);
const props = defineProps({ const props = defineProps({
currJob: { currJob: {
type: Object, type: Object,
default: {}, default: {},
}, },
currElTab: { currElTab: {
type: Object, type: Object,
default: {}, default: {},
}, },
}); });
const data = reactive({ const data = reactive({
// 兼容app端监听printInfo // 兼容app端监听printInfo
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
}, },
// 含可选表单formInfo // 含可选表单formInfo
currFlowFormClone: {}, currFlowFormClone: {},
elTab: null, elTab: null,
initFormData: {}, initFormData: {},
showTinymceView: false, showTinymceView: false,
tinymceTitle: null, tinymceTitle: null,
submitBtn: true submitBtn: true,
}) });
const methods = initFormMethods(formCreateRef, data) const methods = initFormMethods(formCreateRef, data);
const $route = useRoute(); const $route = useRoute();
async function initJobData() { async function initJobData() {
// 兼容移动端 // 兼容移动端
await initJobDataByApp($route, props); await initJobDataByApp($route, props);
// 判断是否存在该表单 // 判断是否存在该表单
data.elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id); data.elTab = orderVue.currElTabIsExist(props.currJob, props.currElTab.id);
handleGetObj(props.currJob.orderId) handleGetObj(props.currJob.orderId);
} }
function handleGetObj(id) { function handleGetObj(id) {
runApplication.getObj(id).then(async resp => { runApplication.getObj(id).then(async (resp) => {
let form = resp.data ? resp.data : {} let form = resp.data ? resp.data : {};
data.currFlowForm = form data.currFlowForm = form;
data.currFlowForm.runJobId = props.currJob.id data.currFlowForm.runJobId = props.currJob.id;
rowEditInitData(form) rowEditInitData(form);
}) });
} }
function rowEditInitData(form: Object) { function rowEditInitData(form: Object) {
let formInfoStr = form.formInfo let formInfoStr = form.formInfo;
// 判断是否为可选表单 // 判断是否为可选表单
if (data.elTab.id !== form.formId) formInfoStr = data.elTab.formInfo if (data.elTab.id !== form.formId) formInfoStr = data.elTab.formInfo;
data.currFlowFormClone = deepClone(form) data.currFlowFormClone = deepClone(form);
data.currFlowFormClone.formInfo = formInfoStr data.currFlowFormClone.formInfo = formInfoStr;
// 记录原始值 // 记录原始值
let formData = parseWithFunctions(form.formData) let formData = parseWithFunctions(form.formData);
Object.assign(data.initFormData, formData) Object.assign(data.initFormData, formData);
} }
async function initFormPermPrint(cloneFormInfo) { async function initFormPermPrint(cloneFormInfo) {
let form = data.currFlowForm let form = data.currFlowForm;
// 判断是否为可选表单 // 判断是否为可选表单
let formType = form.type, formId = form.formId let formType = form.type,
if (data.elTab.id !== form.formId) { formId = form.formId;
formType = data.elTab.type if (data.elTab.id !== form.formId) {
formId = data.elTab.id formType = data.elTab.type;
} formId = data.elTab.id;
let res = await handleDesignFormPerm(props, cloneFormInfo, data.elTab, formType, formId); }
// 判断是否仅查看 let res = await handleDesignFormPerm(props, cloneFormInfo, data.elTab, formType, formId);
await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback, res.widgetList) // 判断是否仅查看
await handleFormPrint(form, formType, formId, '1') await orderVue.currElTabIsView(methods, props.currJob, props.currElTab.id, submitForm, res.callback, res.widgetList);
return data.elTab; await handleFormPrint(form, formType, formId, '1');
} return data.elTab;
}
function printForm() { function printForm() {
data.currFlowForm.formData = formCreateRef.value.design.formData data.currFlowForm.formData = formCreateRef.value.design.formData;
data.currFlowForm.modelRefList = [] data.currFlowForm.modelRefList = [];
let children = formCreateRef.value.design.fApi.children; let children = formCreateRef.value.design.fApi.children;
if (children && children.length > 0) { if (children && children.length > 0) {
children.forEach(each => data.currFlowForm.modelRefList.push({model: each.model})) children.forEach((each) => data.currFlowForm.modelRefList.push({ model: each.model }));
} }
data.currFlowForm.rule = formCreateRef.value.design.rule data.currFlowForm.rule = formCreateRef.value.design.rule;
data.tinymceTitle = data.currFlowForm.formName data.tinymceTitle = data.currFlowForm.formName;
data.showTinymceView = true data.showTinymceView = true;
} }
function closePrint(isSave){ function closePrint(isSave) {
delete data.currFlowForm.modelRefList delete data.currFlowForm.modelRefList;
delete data.currFlowForm.rule delete data.currFlowForm.rule;
if (isSave) delete data.currFlowForm.printInfo if (isSave) delete data.currFlowForm.printInfo;
} }
async function submitForm() { async function submitForm() {
try { try {
loading.value = true; loading.value = true;
let state = await formCreateRef.value.design.fApi.validate(); let state = await formCreateRef.value.design.fApi.validate();
let formData = formCreateRef.value.design.formData let formData = formCreateRef.value.design.formData;
if (!state || validateNull(formData)) { if (!state || validateNull(formData)) {
useMessage().warning("请输入表单信息"); useMessage().warning('请输入表单信息');
return return;
} }
let formJson = saveInitData(formData); let formJson = saveInitData(formData);
await runApplication.putObj(formJson) await runApplication.putObj(formJson);
orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits) orderVue.currElTabIsSave(props.currJob, props.currElTab.id, true, emits);
useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText')); useMessage().success(t(formJson.id ? 'common.editSuccessText' : 'common.addSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().warning("请输入表单信息"); useMessage().warning('请输入表单信息');
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
function saveInitData(form) { function saveInitData(form) {
closePrint(true) closePrint(true);
data.currFlowForm.formData = validateNull(form) ? null : form data.currFlowForm.formData = validateNull(form) ? null : form;
let formJson = deepClone(data.currFlowForm) let formJson = deepClone(data.currFlowForm);
if (!validateNull(form)) { if (!validateNull(form)) {
// 合并不同页面不同字段 // 合并不同页面不同字段
formJson.formData = Object.assign({}, data.initFormData, formJson.formData); formJson.formData = Object.assign({}, data.initFormData, formJson.formData);
} }
formJson.formData = JSON.stringify(formJson.formData) formJson.formData = JSON.stringify(formJson.formData);
return formJson; return formJson;
} }
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currJob.id, () => props.currJob.id,
async () => { async () => {
await initJobData(); await initJobData();
} }
); );
onMounted(async () => { onMounted(async () => {
await initJobData() await initJobData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,52 +1,51 @@
export default { export default {
runApplication: { runApplication: {
index: '#', index: '#',
importrunApplicationTip: 'import RunApplication', importrunApplicationTip: 'import RunApplication',
id: 'id', id: 'id',
code: 'code', code: 'code',
flowKey: 'flowKey', flowKey: 'flowKey',
icon: 'icon', icon: 'icon',
formName: 'formName', formName: 'formName',
groupName: 'groupName', groupName: 'groupName',
finishTime: 'finishTime', finishTime: 'finishTime',
status: 'status', status: 'status',
remark: 'remark', remark: 'remark',
version: 'version', version: 'version',
createUser: 'createUser', createUser: 'createUser',
createTime: 'createTime', createTime: 'createTime',
tableName: 'tableName', tableName: 'tableName',
formInfo: 'formInfo', formInfo: 'formInfo',
sort: 'sort', sort: 'sort',
defFlowId: 'defFlowId', defFlowId: 'defFlowId',
flowInstId: 'flowInstId', flowInstId: 'flowInstId',
formData: 'formData', formData: 'formData',
formId: 'formId', formId: 'formId',
updateUser: 'updateUser', updateUser: 'updateUser',
updateTime: 'updateTime', updateTime: 'updateTime',
delFlag: 'delFlag', delFlag: 'delFlag',
inputIdTip: 'input id', inputIdTip: 'input id',
inputCodeTip: 'input code', inputCodeTip: 'input code',
inputFlowKeyTip: 'input flowKey', inputFlowKeyTip: 'input flowKey',
inputIconTip: 'input icon', inputIconTip: 'input icon',
inputFormNameTip: 'input formName', inputFormNameTip: 'input formName',
inputGroupNameTip: 'input groupName', inputGroupNameTip: 'input groupName',
inputFinishTimeTip: 'input finishTime', inputFinishTimeTip: 'input finishTime',
inputStatusTip: 'input status', inputStatusTip: 'input status',
inputRemarkTip: 'input remark', inputRemarkTip: 'input remark',
inputVersionTip: 'input version', inputVersionTip: 'input version',
inputCreateUserTip: 'input createUser', inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime', inputCreateTimeTip: 'input createTime',
inputTableNameTip: 'input tableName', inputTableNameTip: 'input tableName',
inputFormInfoTip: 'input formInfo', inputFormInfoTip: 'input formInfo',
inputSortTip: 'input sort', inputSortTip: 'input sort',
inputDefFlowIdTip: 'input defFlowId', inputDefFlowIdTip: 'input defFlowId',
inputFlowInstIdTip: 'input flowInstId', inputFlowInstIdTip: 'input flowInstId',
inputFormDataTip: 'input formData', inputFormDataTip: 'input formData',
inputFormIdTip: 'input formId', inputFormIdTip: 'input formId',
inputUpdateUserTip: 'input updateUser', inputUpdateUserTip: 'input updateUser',
inputUpdateTimeTip: 'input updateTime', inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag', inputDelFlagTip: 'input delFlag',
},
} };
}

View File

@@ -1,52 +1,51 @@
export default { export default {
runApplication: { runApplication: {
index: '#', index: '#',
importrunApplicationTip: '导入我的申请', importrunApplicationTip: '导入我的申请',
id: '主键id', id: '主键id',
code: '工单编号', code: '工单编号',
flowKey: '流程名称', flowKey: '流程名称',
icon: '表单图标', icon: '表单图标',
formName: '表单名称', formName: '表单名称',
groupName: '分组名称', groupName: '分组名称',
finishTime: '完成时间', finishTime: '完成时间',
status: '状态', status: '状态',
remark: '表单备注', remark: '表单备注',
version: '版本', version: '版本',
createUser: '创建人', createUser: '创建人',
createTime: '创建时间', createTime: '创建时间',
tableName: '关联表名称', tableName: '关联表名称',
formInfo: '表单信息', formInfo: '表单信息',
sort: '排序值', sort: '排序值',
defFlowId: '流程定义ID', defFlowId: '流程定义ID',
flowInstId: '流程实例ID', flowInstId: '流程实例ID',
formData: '表单数据', formData: '表单数据',
formId: '表单ID', formId: '表单ID',
updateUser: '修改人', updateUser: '修改人',
updateTime: '修改时间', updateTime: '修改时间',
delFlag: '删除标', delFlag: '删除标',
inputIdTip: '请输入主键id', inputIdTip: '请输入主键id',
inputCodeTip: '请输入编号', inputCodeTip: '请输入编号',
inputFlowKeyTip: '请输入流程KEY', inputFlowKeyTip: '请输入流程KEY',
inputIconTip: '请输入表单图标', inputIconTip: '请输入表单图标',
inputFormNameTip: '请输入表单名称', inputFormNameTip: '请输入表单名称',
inputGroupNameTip: '请输入分组名称', inputGroupNameTip: '请输入分组名称',
inputFinishTimeTip: '请输入完成时间', inputFinishTimeTip: '请输入完成时间',
inputStatusTip: '请输入状态', inputStatusTip: '请输入状态',
inputRemarkTip: '请输入表单备注', inputRemarkTip: '请输入表单备注',
inputVersionTip: '请输入版本', inputVersionTip: '请输入版本',
inputCreateUserTip: '请输入创建人', inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间', inputCreateTimeTip: '请输入创建时间',
inputTableNameTip: '请输入关联表名称', inputTableNameTip: '请输入关联表名称',
inputFormInfoTip: '请输入表单信息', inputFormInfoTip: '请输入表单信息',
inputSortTip: '请输入排序值', inputSortTip: '请输入排序值',
inputDefFlowIdTip: '请输入流程定义ID', inputDefFlowIdTip: '请输入流程定义ID',
inputFlowInstIdTip: '请输入流程实例ID', inputFlowInstIdTip: '请输入流程实例ID',
inputFormDataTip: '请输入表单数据', inputFormDataTip: '请输入表单数据',
inputFormIdTip: '请输入表单ID', inputFormIdTip: '请输入表单ID',
inputUpdateUserTip: '请输入修改人', inputUpdateUserTip: '请输入修改人',
inputUpdateTimeTip: '请输入修改时间', inputUpdateTimeTip: '请输入修改时间',
inputDelFlagTip: '请输入删除标', inputDelFlagTip: '请输入删除标',
},
} };
}

View File

@@ -1,362 +1,356 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch"> <el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList"> <el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="$t('runApplication.code')" prop="code" > <el-form-item :label="$t('runApplication.code')" prop="code">
<el-input :placeholder="t('runApplication.inputCodeTip')" v-model="state.queryForm.code" clearable <el-input :placeholder="t('runApplication.inputCodeTip')" v-model="state.queryForm.code" clearable style="max-width: 180px" />
style="max-width: 180px" /> </el-form-item>
</el-form-item> <el-form-item :label="$t('runApplication.flowKey')" prop="flowKey">
<el-form-item :label="$t('runApplication.flowKey')" prop="flowKey" > <el-input :placeholder="t('runApplication.inputFlowKeyTip')" v-model="state.queryForm.flowKey" clearable style="max-width: 180px" />
<el-input :placeholder="t('runApplication.inputFlowKeyTip')" v-model="state.queryForm.flowKey" clearable </el-form-item>
style="max-width: 180px" /> <el-form-item :label="$t('runApplication.formName')" prop="formName">
</el-form-item> <el-input :placeholder="t('runApplication.inputFormNameTip')" v-model="state.queryForm.formName" clearable style="max-width: 180px" />
<el-form-item :label="$t('runApplication.formName')" prop="formName" > </el-form-item>
<el-input :placeholder="t('runApplication.inputFormNameTip')" v-model="state.queryForm.formName" clearable <el-form-item :label="$t('runApplication.status')" prop="status">
style="max-width: 180px" /> <el-select v-model="state.queryForm.status" :placeholder="t('runApplication.inputStatusTip')" clearable style="max-width: 180px">
</el-form-item> <el-option v-for="(item, index) in DIC_PROP.ORDER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option>
<el-form-item :label="$t('runApplication.status')" prop="status" > </el-select>
<el-select v-model="state.queryForm.status" :placeholder="t('runApplication.inputStatusTip')" clearable style="max-width: 180px"> </el-form-item>
<el-option v-for="(item, index) in DIC_PROP.ORDER_STATUS" :key="index" :label="item.label" :value="item.value"></el-option> <el-form-item>
</el-select> <el-button icon="search" type="primary" @click="getDataList">
</el-form-item> {{ $t('common.queryBtn') }}
<el-form-item> </el-button>
<el-button icon="search" type="primary" @click="getDataList"> <el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
{{ $t('common.queryBtn') }} </el-form-item>
</el-button> </el-form>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button> </el-row>
</el-form-item> <el-row>
</el-form> <div class="mb8" style="width: 100%">
</el-row> <el-tooltip placement="top">
<el-row> <template #content>
<div class="mb8" style="width: 100%"> {{ $t('common.delBtn') }}
<el-tooltip placement="top"> </template>
<template #content> <el-button
{{ $t('common.delBtn') }} plain
</template> :disabled="multiple"
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" icon="Delete"
v-auth="'order_runapplication_del'" @click="handleDelete(selectObjs)"> type="primary"
</el-button> class="ml10"
</el-tooltip> v-auth="'order_runapplication_del'"
@click="handleDelete(selectObjs)"
>
</el-button>
</el-tooltip>
<right-toolbar v-model:showSearch="showSearch" :export="'order_runapplication_export'" <right-toolbar
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px" v-model:showSearch="showSearch"
@queryTable="getDataList"></right-toolbar> :export="'order_runapplication_export'"
</div> @exportExcel="exportExcel"
</el-row> class="ml10"
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" style="float: right; margin-right: 20px"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle"> @queryTable="getDataList"
<el-table-column type="selection" width="40" align="center"/> ></right-toolbar>
<el-table-column type="index" :label="t('runApplication.index')" width="40"/> </div>
<el-table-column prop="code" :label="t('runApplication.code')" show-overflow-tooltip/> </el-row>
<el-table-column prop="flowKey" :label="t('runApplication.flowKey')" show-overflow-tooltip> <el-table
<template #default="scope"> :data="state.dataList"
<convert-name :options="dicData.flowKey" :value="scope.row.flowKey" v-loading="state.loading"
:valueKey="'flowKey'" :showKey="'flowName'"></convert-name> style="width: 100%"
</template> @selection-change="handleSelectionChange"
</el-table-column> @sort-change="sortChangeHandle"
<el-table-column prop="icon" :label="t('runApplication.icon')" show-overflow-tooltip> >
<template #default="scope"> <el-table-column type="selection" width="40" align="center" />
<SvgIcon :name="scope.row.icon" :size="20" color="#409EFF"/> <el-table-column type="index" :label="t('runApplication.index')" width="40" />
</template> <el-table-column prop="code" :label="t('runApplication.code')" show-overflow-tooltip />
</el-table-column> <el-table-column prop="flowKey" :label="t('runApplication.flowKey')" show-overflow-tooltip>
<el-table-column prop="formName" :label="t('runApplication.formName')" show-overflow-tooltip/> <template #default="scope">
<el-table-column prop="groupName" :label="t('runApplication.groupName')" show-overflow-tooltip/> <convert-name :options="dicData.flowKey" :value="scope.row.flowKey" :valueKey="'flowKey'" :showKey="'flowName'"></convert-name>
<el-table-column prop="finishTime" :label="t('runApplication.finishTime')" show-overflow-tooltip/> </template>
<el-table-column prop="status" :label="t('runApplication.status')" show-overflow-tooltip> </el-table-column>
<template #default="scope"> <el-table-column prop="icon" :label="t('runApplication.icon')" show-overflow-tooltip>
<dict-tag :options="DIC_PROP.ORDER_STATUS" :value="scope.row.status"></dict-tag> <template #default="scope">
</template> <SvgIcon :name="scope.row.icon" :size="20" color="#409EFF" />
</el-table-column> </template>
<el-table-column prop="remark" :label="t('runApplication.remark')" width="200" show-overflow-tooltip/> </el-table-column>
<el-table-column prop="version" :label="t('runApplication.version')" show-overflow-tooltip/> <el-table-column prop="formName" :label="t('runApplication.formName')" show-overflow-tooltip />
<!-- <el-table-column prop="createUser" :label="t('runApplication.createUser')" show-overflow-tooltip> <el-table-column prop="groupName" :label="t('runApplication.groupName')" show-overflow-tooltip />
<el-table-column prop="finishTime" :label="t('runApplication.finishTime')" show-overflow-tooltip />
<el-table-column prop="status" :label="t('runApplication.status')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="DIC_PROP.ORDER_STATUS" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="remark" :label="t('runApplication.remark')" width="200" show-overflow-tooltip />
<el-table-column prop="version" :label="t('runApplication.version')" show-overflow-tooltip />
<!-- <el-table-column prop="createUser" :label="t('runApplication.createUser')" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<convert-name :options="state.dicData.createUser" :value="scope.row.createUser" <convert-name :options="state.dicData.createUser" :value="scope.row.createUser"
:valueKey="'userId'" :showKey="'name'"></convert-name> :valueKey="'userId'" :showKey="'name'"></convert-name>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" :label="t('runApplication.createTime')" show-overflow-tooltip/>--> <el-table-column prop="createTime" :label="t('runApplication.createTime')" show-overflow-tooltip/>-->
<el-table-column :label="$t('common.action')" width="170"> <el-table-column :label="$t('common.action')" width="170">
<template #default="scope"> <template #default="scope">
<el-tooltip placement="top">
<template #content>
{{ $t('common.viewBtn') }}
</template>
<el-button text type="primary" icon="view" @click="handleViewOrder(scope.row)"> </el-button>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top">
<template #content> <template #content> 打印表单 </template>
{{ $t('common.viewBtn') }} <el-button icon="Document" text type="primary" @click="handleViewOrder(scope.row)"> </el-button>
</template> </el-tooltip>
<el-button text type="primary" icon="view" @click="handleViewOrder(scope.row)">
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top">
<template #content> <template #content>
打印表单 {{ $t('common.copyBtn') }}
</template> </template>
<el-button icon="Document" text type="primary" @click="handleViewOrder(scope.row)"> <el-button icon="DocumentCopy" text type="primary" @click="handleInitiateOrder(scope.row, 'copy')"> </el-button>
</el-button> </el-tooltip>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[2].value">
<template #content> <template #content>
{{ $t('common.copyBtn') }} {{ $t('jfI18n.recallBtn') }}
</template> </template>
<el-button icon="DocumentCopy" text type="primary" @click="handleInitiateOrder(scope.row,'copy')"> <el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
</el-button> </el-tooltip>
</el-tooltip> <el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[0].value">
<template #content>
{{ $t('jfI18n.resetBtn') }}
</template>
<el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)"> </el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[2].value"> <el-tooltip placement="top">
<template #content> <template #content>
{{ $t('jfI18n.recallBtn') }} {{ $t('jfI18n.viewFlow') }}
</template> </template>
<el-button icon="RefreshLeft" text type="primary" @click="handleRecallReset(scope.row)"> <el-button icon="Share" text type="primary" @click="openPreview(scope.row)"> </el-button>
</el-button> </el-tooltip>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[0].value">
<template #content>
{{ $t('jfI18n.resetBtn') }}
</template>
<el-button icon="RefreshRight" text type="primary" @click="handleRecallReset(scope.row)">
</el-button>
</el-tooltip>
<el-tooltip placement="top"> <el-tooltip
<template #content> placement="top"
{{ $t('jfI18n.viewFlow') }} v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value || scope.row.status === DIC_PROP.ORDER_STATUS[0].value"
</template> >
<el-button icon="Share" text type="primary" @click="openPreview(scope.row)"> <template #content>
</el-button> {{ $t('common.editBtn') }}
</el-tooltip> </template>
<el-button icon="edit-pen" text type="primary" @click="handleInitiateOrder(scope.row, 'edit')"> </el-button>
</el-tooltip>
<el-tooltip placement="top" v-if="scope.row.status === DIC_PROP.ORDER_STATUS[1].value">
<template #content>
{{ $t('common.delBtn') }}
</template>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value || scope.row.status===DIC_PROP.ORDER_STATUS[0].value"> <json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="handleInitiateOrder">
<template #content> <template v-slot="slotProps" v-if="data.showInitiateOrder">
{{ $t('common.editBtn') }} <run-initiate
</template> ref="form"
<el-button icon="edit-pen" text type="primary" @click="handleInitiateOrder(scope.row,'edit')"> v-show="slotProps.currActive === 'form'"
</el-button> :curr-flow-form="data.currFlowForm"
</el-tooltip> @handleInitiateOrder="handleInitiateOrder"
<el-tooltip placement="top" v-if="scope.row.status===DIC_PROP.ORDER_STATUS[1].value"> ></run-initiate>
<template #content> </template>
{{ $t('common.delBtn') }} <template v-slot="slotProps" v-if="data.showHandleForm">
</template> <custom-form
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])"> ref="form"
</el-button> v-show="slotProps.currActive === 'form'"
</el-tooltip> :curr-job="data.currFlowForm"
</template> @onHandleForm="handleInitiateOrder"
</el-table-column> ></custom-form>
</el-table> </template>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" /> </json-flow-predict>
</div>
<json-flow-predict ref="predict" :proxy="proxy" @handleInitiateOrder="handleInitiateOrder"> <!-- 查看工单 -->
<template v-slot="slotProps" v-if="data.showInitiateOrder"> <el-dialog v-model="data.showHandleFormView" top="20px" width="90%" title="查看工单" append-to-body>
<run-initiate ref="form" v-show="slotProps.currActive === 'form'" :curr-flow-form="data.currFlowForm" <custom-form v-if="data.showHandleFormView" :curr-job="data.currFlowForm"></custom-form>
@handleInitiateOrder="handleInitiateOrder"></run-initiate> </el-dialog>
</template>
<template v-slot="slotProps" v-if="data.showHandleForm">
<custom-form ref="form" v-show="slotProps.currActive === 'form'" :curr-job="data.currFlowForm"
@onHandleForm="handleInitiateOrder"></custom-form>
</template>
</json-flow-predict>
<!-- 查看工单 --> <!-- 查看工单 -->
<el-dialog <el-dialog v-model="data.showViewOrder" top="20px" width="90%" title="查看工单" append-to-body>
v-model="data.showHandleFormView" <run-view v-if="data.showViewOrder" :curr-flow-form="data.currFlowForm"></run-view>
top="20px" </el-dialog>
width="90%"
title="查看工单"
append-to-body>
<custom-form v-if="data.showHandleFormView" :curr-job="data.currFlowForm"></custom-form>
</el-dialog>
<!-- 查看工单 --> <!-- 查看流程图 -->
<el-dialog <el-drawer class="flow-overflow-drawer" direction="rtl" append-to-body size="90%" v-model="data.showFlowPic">
v-model="data.showViewOrder" <flow-photo v-if="data.showFlowPic" :curr-job="data.currFlowForm"></flow-photo>
top="20px" </el-drawer>
width="90%" </div>
title="查看工单"
append-to-body>
<run-view v-if="data.showViewOrder" :curr-flow-form="data.currFlowForm"></run-view>
</el-dialog>
<!-- 查看流程图 -->
<el-drawer
class="flow-overflow-drawer" direction="rtl"
append-to-body size="90%"
v-model="data.showFlowPic"
>
<flow-photo v-if="data.showFlowPic" :curr-job="data.currFlowForm"></flow-photo>
</el-drawer>
</div>
</template> </template>
<script setup lang="ts" name="systemRunApplication"> <script setup lang="ts" name="systemRunApplication">
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from '/@/hooks/table';
import * as runApplication from '/@/api/order/run-application' import * as runApplication from '/@/api/order/run-application';
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from '/@/hooks/message';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {onLoadDicUrl, onLoaded} from "/@/flow/components/convert-name/convert"; import { onLoadDicUrl, onLoaded } from '/@/flow/components/convert-name/convert';
import * as common from '/@/flow/support/common' import * as common from '/@/flow/support/common';
import other, {deepClone} from "/@/utils/other"; import other, { deepClone } from '/@/utils/other';
import {recallReset} from "/@/api/jsonflow/run-flow"; import { recallReset } from '/@/api/jsonflow/run-flow';
import {DIC_PROP} from "/@/flow/support/dict-prop"; import { DIC_PROP } from '/@/flow/support/dict-prop';
import {openFlowPreview} from "/@/flow/support/extend"; import { openFlowPreview } from '/@/flow/support/extend';
import {handleCustomForm, vueKey} from "/@/api/order/order-key-vue"; import { handleCustomForm, vueKey } from '/@/api/order/order-key-vue';
// 引入组件 // 引入组件
const FlowPhoto = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/view.vue')); const FlowPhoto = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/view.vue'));
const RunInitiate = defineAsyncComponent(() => import('./initiate.vue')); const RunInitiate = defineAsyncComponent(() => import('./initiate.vue'));
const RunView = defineAsyncComponent(() => import('./view.vue')); const RunView = defineAsyncComponent(() => import('./view.vue'));
const CustomForm = defineAsyncComponent(() => import('/@/flow/components/custom-form/handle.vue')); const CustomForm = defineAsyncComponent(() => import('/@/flow/components/custom-form/handle.vue'));
const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue')); const JsonFlowPredict = defineAsyncComponent(() => import('/@/views/jsonflow/flow-design/predict.vue'));
const { t } = useI18n() const { t } = useI18n();
const {proxy} = getCurrentInstance(); const { proxy } = getCurrentInstance();
// 搜索变量 // 搜索变量
const queryRef = ref() const queryRef = ref();
const showSearch = ref(true) const showSearch = ref(true);
// 多选变量 // 多选变量
const selectObjs = ref([]) as any const selectObjs = ref([]) as any;
const multiple = ref(true) const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {}, queryForm: {},
pageList: runApplication.fetchList, pageList: runApplication.fetchList,
onLoaded: onLoaded({key: "createUser"}), onLoaded: onLoaded({ key: 'createUser' }),
descs: ["create_time"] descs: ['create_time'],
}) });
// 定义字典 // 定义字典
const dicData = reactive({}); const dicData = reactive({});
const onLoad = onLoadDicUrl({key: "flowKey"}); const onLoad = onLoadDicUrl({ key: 'flowKey' });
onMounted(() => { onMounted(() => {
onLoad(dicData); onLoad(dicData);
}); });
// table hook // table hook
const { const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile } = useTable(state);
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile
} = useTable(state)
// 清空搜索条件 // 清空搜索条件
const resetQuery = () => { const resetQuery = () => {
// 清空搜索条件 // 清空搜索条件
queryRef.value?.resetFields() queryRef.value?.resetFields();
// 清空多选 // 清空多选
selectObjs.value = [] selectObjs.value = [];
getDataList() getDataList();
} };
// 导出excel // 导出excel
const exportExcel = () => { const exportExcel = () => {
downBlobFile('/order/run-application/export', state.queryForm, 'run-application.xlsx') downBlobFile('/order/run-application/export', state.queryForm, 'run-application.xlsx');
} };
// 多选事件 // 多选事件
const handleSelectionChange = (objs: any) => { const handleSelectionChange = (objs: any) => {
selectObjs.value = objs.map(({ id }) => id); selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length; multiple.value = !objs.length;
}; };
// 删除操作 // 删除操作
const handleDelete = async (ids: string[]) => { const handleDelete = async (ids: string[]) => {
try { try {
await useMessageBox().confirm(t('common.delConfirmText')); await useMessageBox().confirm(t('common.delConfirmText'));
} catch { } catch {
return; return;
} }
try { try {
await runApplication.delObjs(ids); await runApplication.delObjs(ids);
getDataList(); getDataList();
useMessage().success(t('common.delSuccessText')); useMessage().success(t('common.delSuccessText'));
} catch (err: any) { } catch (err: any) {
useMessage().error(err.msg); useMessage().error(err.msg);
} }
}; };
const data = reactive({ const data = reactive({
showViewOrder: false, showViewOrder: false,
showInitiateOrder: false, showInitiateOrder: false,
showFlowPic: false, showFlowPic: false,
currFlowForm: {}, currFlowForm: {},
showHandleFormView: false, showHandleFormView: false,
showHandleForm: false, showHandleForm: false,
}) });
function handleViewOrder(row) { function handleViewOrder(row) {
data.currFlowForm = row data.currFlowForm = row;
// 判断是否自定义首页 // 判断是否自定义首页
if (row.path !== vueKey.RunApplicationForm) { if (row.path !== vueKey.RunApplicationForm) {
handleCustomForm(data, row) handleCustomForm(data, row);
data.currFlowForm.operType = 'view' data.currFlowForm.operType = 'view';
data.showHandleFormView = true data.showHandleFormView = true;
} else { } else {
data.showViewOrder = true data.showViewOrder = true;
} }
} }
function handleInitiateOrder(row, operType) { function handleInitiateOrder(row, operType) {
if (row === false) { if (row === false) {
getDataList(); getDataList();
openPredict({}, false) openPredict({}, false);
data.showInitiateOrder = false data.showInitiateOrder = false;
data.showHandleForm = false data.showHandleForm = false;
return return;
} }
data.currFlowForm = deepClone(row) data.currFlowForm = deepClone(row);
data.currFlowForm.operType = operType data.currFlowForm.operType = operType;
let clone = {operType: data.currFlowForm.operType}; let clone = { operType: data.currFlowForm.operType };
common.handleClone(clone, data.currFlowForm) common.handleClone(clone, data.currFlowForm);
// 判断是否自定义首页 // 判断是否自定义首页
if (row.path !== vueKey.RunApplicationForm) { if (row.path !== vueKey.RunApplicationForm) {
handleCustomForm(data, row) handleCustomForm(data, row);
data.showHandleForm = true data.showHandleForm = true;
} else { } else {
data.showInitiateOrder = true data.showInitiateOrder = true;
} }
openPredict(row, true) openPredict(row, true);
} }
function openPredict(row, bool) { function openPredict(row, bool) {
proxy.$refs.predict.open(row, bool) proxy.$refs.predict.open(row, bool);
} }
const $router = useRouter(); const $router = useRouter();
function openPreview(row) { function openPreview(row) {
if (row.status === DIC_PROP.ORDER_STATUS[1].value) { if (row.status === DIC_PROP.ORDER_STATUS[1].value) {
data.currFlowForm = {defFlowId: row.defFlowId} data.currFlowForm = { defFlowId: row.defFlowId };
data.showFlowPic = true data.showFlowPic = true;
} else { } else {
openFlowPreview($router, {flowInstId: row.flowInstId}, '1') openFlowPreview($router, { flowInstId: row.flowInstId }, '1');
} }
} }
function handleRecallReset(row) {
let params = {id: row.flowInstId, flowKey: row.flowKey, status: row.status}
if (row.status === DIC_PROP.ORDER_STATUS[0].value) {
recallReset(params).then(() => {
useMessage().success('重发成功');
getDataList();
})
return
}
useMessageBox().confirm('是否确认要撤回该工单?')
.then(() => {
return recallReset(params)
}).then(() => {
useMessage().success('撤回成功')
getDataList();
})
}
function handleRecallReset(row) {
let params = { id: row.flowInstId, flowKey: row.flowKey, status: row.status };
if (row.status === DIC_PROP.ORDER_STATUS[0].value) {
recallReset(params).then(() => {
useMessage().success('重发成功');
getDataList();
});
return;
}
useMessageBox()
.confirm('是否确认要撤回该工单?')
.then(() => {
return recallReset(params);
})
.then(() => {
useMessage().success('撤回成功');
getDataList();
});
}
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../../flow/components/style/flow-drawer.scss"; @import '../../../flow/components/style/flow-drawer.scss';
</style> </style>

View File

@@ -1,125 +1,116 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<form-render ref="formCreateRef" :currFlowForm="props.currFlowForm" :initFormPermPrint="initFormPermPrint"> <form-render ref="formCreateRef" :currFlowForm="props.currFlowForm" :initFormPermPrint="initFormPermPrint"> </form-render>
</form-render> </div>
</div> <footer class="el-dialog__footer" v-if="data.submitBtn">
<footer class="el-dialog__footer" v-if="data.submitBtn"> <span class="dialog-footer">
<span class="dialog-footer"> <template v-if="props.currFlowForm.status !== DIC_PROP.ORDER_STATUS[0].value">
<template v-if="props.currFlowForm.status !== DIC_PROP.ORDER_STATUS[0].value"> <el-button type="primary" @click="submitForm" :disabled="loading">{{ t('jfI18n.submit') }} </el-button>
<el-button type="primary" @click="submitForm" :disabled="loading">{{ <el-button type="primary" @click="handleTempStore" :disabled="loading">{{ t('jfI18n.temp') }} </el-button>
t('jfI18n.submit') </template>
}} <template v-else>
</el-button> <el-button type="primary" @click="submitForm" :disabled="loading">{{ $t('common.editBtn') }}</el-button>
<el-button type="primary" @click="handleTempStore" :disabled="loading">{{ </template>
t('jfI18n.temp') </span>
}} </footer>
</el-button> </div>
</template>
<template v-else>
<el-button type="primary" @click="submitForm" :disabled="loading">{{
$t('common.editBtn')
}}</el-button>
</template>
</span>
</footer>
</div>
</template> </template>
<script setup lang="ts" name="RunApplicationInitiate"> <script setup lang="ts" name="RunApplicationInitiate">
import * as runApplication from '/@/api/order/run-application' import * as runApplication from '/@/api/order/run-application';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {validateNull} from "/@/utils/validate"; import { validateNull } from '/@/utils/validate';
import {deepClone} from "/@/utils/other"; import { deepClone } from '/@/utils/other';
import * as common from "/@/flow/support/common"; import * as common from '/@/flow/support/common';
import {doInitData, doInitiateForm, doTempStore, initFormMethods, initJobDataByApp} from "../index"; import { doInitData, doInitiateForm, doTempStore, initFormMethods, initJobDataByApp } from '../index';
import {handleFormStartPerm} from "/@/flow/utils/form-perm"; import { handleFormStartPerm } from '/@/flow/utils/form-perm';
import {currFormIsView} from "/@/api/order/order-key-vue"; import { currFormIsView } from '/@/api/order/order-key-vue';
const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue')); const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue'));
const formCreateRef = ref(null) const formCreateRef = ref(null);
const {t} = useI18n(); const { t } = useI18n();
const $emit = defineEmits(['handleInitiateOrder']); const $emit = defineEmits(['handleInitiateOrder']);
const loading = ref(false); const loading = ref(false);
const props = defineProps({ const props = defineProps({
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
} },
}); });
const data = reactive({ const data = reactive({
submitBtn: true submitBtn: true,
}); });
const $route = useRoute(); const $route = useRoute();
function initJobData() { function initJobData() {
initJobDataByApp($route, handleGetObj, null) initJobDataByApp($route, handleGetObj, null);
} }
function handleGetObj(id) { function handleGetObj(id) {
runApplication.getObj(id).then(resp => { runApplication.getObj(id).then((resp) => {
let form = resp.data ? resp.data : {} let form = resp.data ? resp.data : {};
Object.assign(props.currFlowForm, form); Object.assign(props.currFlowForm, form);
}) });
} }
async function submitForm() { async function submitForm() {
await doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t) await doInitiateForm(loading, props, data, $route, formCreateRef, $emit, saveInitData, t);
} }
function handleTempStore() { function handleTempStore() {
doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData) doTempStore(loading, data, $route, formCreateRef, $emit, saveInitData);
} }
const methods = initFormMethods(formCreateRef, data) const methods = initFormMethods(formCreateRef, data);
async function initFormPermPrint(formInfo) { async function initFormPermPrint(formInfo) {
// 处理表单权限 // 处理表单权限
let res = await handleFormStartPerm(null, null, formInfo, props.currFlowForm.defFlowId, null, props.currFlowForm.type) let res = await handleFormStartPerm(null, null, formInfo, props.currFlowForm.defFlowId, null, props.currFlowForm.type);
await currFormIsView(methods, res.elTab, true, res.callback, res.widgetList) await currFormIsView(methods, res.elTab, true, res.callback, res.widgetList);
return res.elTab return res.elTab;
} }
function saveInitData(form) { function saveInitData(form) {
props.currFlowForm.formData = validateNull(form) ? undefined : form props.currFlowForm.formData = validateNull(form) ? undefined : form;
let formJson = deepClone(props.currFlowForm) let formJson = deepClone(props.currFlowForm);
let clone = {operType: props.currFlowForm.operType, form: formJson}; let clone = { operType: props.currFlowForm.operType, form: formJson };
common.handleCloneSubmit(clone) common.handleCloneSubmit(clone);
formJson.formData = JSON.stringify(formJson.formData) formJson.formData = JSON.stringify(formJson.formData);
return formJson; return formJson;
} }
async function getFormData() { async function getFormData() {
return await doInitData(formCreateRef, saveInitData) return await doInitData(formCreateRef, saveInitData);
} }
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
getFormData, getFormData,
}) });
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currFlowForm.id, () => props.currFlowForm.id,
() => { () => {
initJobData(); initJobData();
} }
); );
onMounted(() => { onMounted(() => {
initJobData() initJobData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,115 +1,114 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<form-render ref="formCreateRef" :currFlowForm="form.currFlowForm" v-if="!validateNull(form.currFlowForm)" :renderType="'-1'" <form-render
:initFormPermPrint="initFormPermPrint"> ref="formCreateRef"
</form-render> :currFlowForm="form.currFlowForm"
</div> v-if="!validateNull(form.currFlowForm)"
<footer class="el-dialog__footer"> :renderType="'-1'"
<span class="dialog-footer"> :initFormPermPrint="initFormPermPrint"
<el-button type="primary" @click="printForm" v-if="form.currFlowForm.printInfo">{{ >
t('jfI18n.print') </form-render>
}} </div>
</el-button> <footer class="el-dialog__footer">
</span> <span class="dialog-footer">
</footer> <el-button type="primary" @click="printForm" v-if="form.currFlowForm.printInfo">{{ t('jfI18n.print') }} </el-button>
</span>
</footer>
<!-- 打印表单 --> <!-- 打印表单 -->
<el-dialog v-model="form.showTinymceView" top="20px" width="700px" <el-dialog v-model="form.showTinymceView" top="20px" width="700px" :title="form.tinymceTitle" append-to-body @close="closePrint">
:title="form.tinymceTitle" append-to-body <tinymce-view v-if="form.showTinymceView" :currFlowForm="form.currFlowForm"></tinymce-view>
@close="closePrint"> </el-dialog>
<tinymce-view v-if="form.showTinymceView" :currFlowForm="form.currFlowForm"></tinymce-view> </div>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts" name="RunApplicationView"> <script setup lang="ts" name="RunApplicationView">
import * as runApplication from "/@/api/order/run-application"; import * as runApplication from '/@/api/order/run-application';
import {useI18n} from "vue-i18n"; import { useI18n } from 'vue-i18n';
import {initJobDataByApp} from "../index"; import { initJobDataByApp } from '../index';
import {deepClone} from "/@/utils/other"; import { deepClone } from '/@/utils/other';
import {handleFormPrint} from "/@/flow/utils/form-perm"; import { handleFormPrint } from '/@/flow/utils/form-perm';
const {t} = useI18n(); const { t } = useI18n();
const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue')); const FormRender = defineAsyncComponent(() => import('/@/flow/components/form-create/render.vue'));
const formCreateRef = ref(null) const formCreateRef = ref(null);
// 引入组件 // 引入组件
const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue')); const TinymceView = defineAsyncComponent(() => import('/@/flow/components/tinymce/TinymceView.vue'));
const props = defineProps({ const props = defineProps({
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
} },
}); });
const form = reactive({ const form = reactive({
// 兼容app端监听printInfo // 兼容app端监听printInfo
currFlowForm: { currFlowForm: {
type: Object, type: Object,
default: {}, default: {},
}, },
showTinymceView: false, showTinymceView: false,
tinymceTitle: null, tinymceTitle: null,
}); });
const $route = useRoute(); const $route = useRoute();
function initJobData() { function initJobData() {
initJobDataByApp($route, handleGetObj, () => { initJobDataByApp($route, handleGetObj, () => {
form.currFlowForm = props.currFlowForm form.currFlowForm = props.currFlowForm;
}) });
} }
function handleGetObj(id) { function handleGetObj(id) {
runApplication.getObj(id).then(resp => { runApplication.getObj(id).then((resp) => {
let formData = resp.data ? resp.data : {} let formData = resp.data ? resp.data : {};
Object.assign(form.currFlowForm, formData); Object.assign(form.currFlowForm, formData);
}) });
} }
async function initFormPermPrint() { async function initFormPermPrint() {
await handleFormPrint(form.currFlowForm, form.currFlowForm.type, form.currFlowForm.formId, '1') await handleFormPrint(form.currFlowForm, form.currFlowForm.type, form.currFlowForm.formId, '1');
} }
function printForm() { function printForm() {
form.currFlowForm.formData = formCreateRef.value.design.formData form.currFlowForm.formData = formCreateRef.value.design.formData;
form.currFlowForm.modelRefList = [] form.currFlowForm.modelRefList = [];
let children = formCreateRef.value.design.fApi.children; let children = formCreateRef.value.design.fApi.children;
if (children && children.length > 0) { if (children && children.length > 0) {
// 防止!validateNull(form.currFlowForm)引用循环 // 防止!validateNull(form.currFlowForm)引用循环
children.forEach(each => form.currFlowForm.modelRefList.push({model: each.model})) children.forEach((each) => form.currFlowForm.modelRefList.push({ model: each.model }));
} }
form.currFlowForm.rule = formCreateRef.value.design.rule form.currFlowForm.rule = formCreateRef.value.design.rule;
form.tinymceTitle = form.currFlowForm.formName form.tinymceTitle = form.currFlowForm.formName;
form.showTinymceView = true form.showTinymceView = true;
} }
function closePrint(){ function closePrint() {
delete form.currFlowForm.modelRefList delete form.currFlowForm.modelRefList;
delete form.currFlowForm.rule delete form.currFlowForm.rule;
} }
// 监听双向绑定 // 监听双向绑定
watch( watch(
() => props.currFlowForm.id, () => props.currFlowForm.id,
() => { () => {
initJobData(); initJobData();
} }
); );
onMounted(() => { onMounted(() => {
initJobData() initJobData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-dialog__footer { .el-dialog__footer {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
} }
</style> </style>

View File

@@ -1,236 +1,202 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 --> <!-- 操作按钮 -->
<el-row> <el-row>
<div class="mb15" style="width: 100%;"> <div class="mb15" style="width: 100%">
<el-button <el-button v-if="hasAuth('professional_professionalacademicqualificationsconfig_add')" type="primary" icon="FolderAdd" @click="handleAdd"
v-if="hasAuth('professional_professionalacademicqualificationsconfig_add')" >
type="primary" </el-button>
icon="FolderAdd" </div>
@click="handleAdd"> </el-row>
</el-button> <!-- 表格 -->
</div> <el-table
</el-row> ref="tableRef"
<!-- 表格 --> :data="state.dataList"
<el-table v-loading="state.loading"
ref="tableRef" border
:data="state.dataList" row-key="id"
v-loading="state.loading" :cell-style="tableStyle.cellStyle"
border :header-cell-style="tableStyle.headerCellStyle"
row-key="id" >
:cell-style="tableStyle.cellStyle" <el-table-column type="index" label="序号" width="60" align="center" />
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="qualificationName" label="学历名称" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="hasAuth('professional_professionalacademicqualificationsconfig_edit')"
icon="edit-pen"
link
type="primary"
@click="handleEdit(scope.row)">修改
</el-button>
<el-button
v-if="hasAuth('professional_professionalacademicqualificationsconfig_del')"
icon="delete"
link
type="danger"
style="margin-left: 12px"
@click="handleDel(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 --> <el-table-column prop="qualificationName" label="学历名称" min-width="150" align="center" show-overflow-tooltip />
<pagination
v-bind="state.pagination"
@current-change="currentChangeHandle"
@size-change="sizeChangeHandle"
/>
<!-- 新增/编辑弹窗 --> <el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-dialog
v-model="dialogVisible" <el-table-column prop="sort" label="排序" width="100" align="center" />
:title="form.id ? '修改' : '新增'"
width="600px" <el-table-column prop="remarks" label="备注" min-width="200" align="center" show-overflow-tooltip />
:close-on-click-modal="false"
destroy-on-close <el-table-column label="操作" width="150" align="center" fixed="right">
> <template #default="scope">
<el-form <el-button
ref="formRef" v-if="hasAuth('professional_professionalacademicqualificationsconfig_edit')"
:model="form" icon="edit-pen"
:rules="formRules" link
label-width="120px" type="primary"
> @click="handleEdit(scope.row)"
<el-form-item label="学历名称" prop="qualificationName"> >修改
<el-input </el-button>
v-model="form.qualificationName" <el-button
placeholder="请输入学历名称" v-if="hasAuth('professional_professionalacademicqualificationsconfig_del')"
clearable icon="delete"
/> link
</el-form-item> type="danger"
style="margin-left: 12px"
<el-form-item label="排序" prop="sort"> @click="handleDel(scope.row)"
<el-input-number >删除
v-model="form.sort" </el-button>
:min="0" </template>
placeholder="请输入排序" </el-table-column>
style="width: 100%" </el-table>
/>
</el-form-item> <!-- 分页 -->
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle" />
<el-form-item label="备注" prop="remarks">
<el-input <!-- 新增/编辑弹窗 -->
v-model="form.remarks" <el-dialog v-model="dialogVisible" :title="form.id ? '修改' : '新增'" width="600px" :close-on-click-modal="false" destroy-on-close>
type="textarea" <el-form ref="formRef" :model="form" :rules="formRules" label-width="120px">
:rows="3" <el-form-item label="学历名称" prop="qualificationName">
placeholder="请输入备注" <el-input v-model="form.qualificationName" placeholder="请输入学历名称" clearable />
clearable </el-form-item>
/>
</el-form-item> <el-form-item label="排序" prop="sort">
</el-form> <el-input-number v-model="form.sort" :min="0" placeholder="请输入排序" style="width: 100%" />
</el-form-item>
<template #footer>
<div class="dialog-footer"> <el-form-item label="备注" prop="remarks">
<el-button @click="dialogVisible = false">取消</el-button> <el-input v-model="form.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button> </el-form-item>
</div> </el-form>
</template>
</el-dialog> <template #footer>
</div> <div class="dialog-footer">
</div> <el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue';
import { useAuth } from '/@/hooks/auth' import { useAuth } from '/@/hooks/auth';
import { BasicTableProps, useTable } from '/@/hooks/table' import { BasicTableProps, useTable } from '/@/hooks/table';
import { useMessage } from '/@/hooks/message' import { useMessage } from '/@/hooks/message';
import { useMessageBox } from '/@/hooks/message' import { useMessageBox } from '/@/hooks/message';
import { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/academicqualificationsconfig' import { fetchList, addObj, putObj, delObj } from '/@/api/professional/rsbase/academicqualificationsconfig';
const { hasAuth } = useAuth() const { hasAuth } = useAuth();
// 消息提示 hooks // 消息提示 hooks
const message = useMessage() const message = useMessage();
const messageBox = useMessageBox() const messageBox = useMessageBox();
// 表格引用 // 表格引用
const tableRef = ref() const tableRef = ref();
const formRef = ref() const formRef = ref();
// 弹窗状态 // 弹窗状态
const dialogVisible = ref(false) const dialogVisible = ref(false);
const submitLoading = ref(false) const submitLoading = ref(false);
// 表单数据 // 表单数据
const form = reactive({ const form = reactive({
id: '', id: '',
qualificationName: '', qualificationName: '',
sort: 0, sort: 0,
remarks: '' remarks: '',
}) });
// 表单验证规则 // 表单验证规则
const formRules = { const formRules = {
qualificationName: [ qualificationName: [{ required: true, message: '请输入学历名称', trigger: 'blur' }],
{ required: true, message: '请输入学历名称', trigger: 'blur' } sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
], };
sort: [
{ required: true, message: '请输入排序', trigger: 'blur' }
]
}
// 配置 useTable // 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({ const state: BasicTableProps = reactive<BasicTableProps>({
pageList: async (params: any) => { pageList: async (params: any) => {
const response = await fetchList(params) const response = await fetchList(params);
return { return {
data: { data: {
records: response.data.records || [], records: response.data.records || [],
total: response.data.total || 0 total: response.data.total || 0,
} },
} };
}, },
queryForm: {} queryForm: {},
}) });
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state) const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
// 打开新增窗口 // 打开新增窗口
const handleAdd = () => { const handleAdd = () => {
Object.assign(form, { Object.assign(form, {
id: '', id: '',
qualificationName: '', qualificationName: '',
sort: 0, sort: 0,
remarks: '' remarks: '',
}) });
dialogVisible.value = true dialogVisible.value = true;
} };
// 打开编辑窗口 // 打开编辑窗口
const handleEdit = async (row: any) => { const handleEdit = async (row: any) => {
Object.assign(form, { Object.assign(form, {
id: row.id, id: row.id,
qualificationName: row.qualificationName || '', qualificationName: row.qualificationName || '',
sort: row.sort || 0, sort: row.sort || 0,
remarks: row.remarks || '' remarks: row.remarks || '',
}) });
dialogVisible.value = true dialogVisible.value = true;
} };
// 删除 // 删除
const handleDel = (row: any) => { const handleDel = (row: any) => {
messageBox.confirm('是否确认删除该条记录').then(async () => { messageBox
await delObj(row.id) .confirm('是否确认删除该条记录')
message.success('删除成功') .then(async () => {
// 如果当前页只剩一条数据,且不是第一页,则跳转到上一页 await delObj(row.id);
if (state.pagination && state.dataList && state.dataList.length === 1 && state.pagination.current && state.pagination.current > 1) { message.success('删除成功');
state.pagination.current = state.pagination.current - 1 // 如果当前页只剩一条数据,且不是第一页,则跳转到上一页
} if (state.pagination && state.dataList && state.dataList.length === 1 && state.pagination.current && state.pagination.current > 1) {
getDataList() state.pagination.current = state.pagination.current - 1;
}).catch(() => {}) }
} getDataList();
})
.catch(() => {});
};
// 提交表单 // 提交表单
const handleSubmit = async () => { const handleSubmit = async () => {
if (!formRef.value) return if (!formRef.value) return;
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (form.id) {
await putObj(form)
message.success('修改成功')
} else {
await addObj(form)
message.success('添加成功')
}
dialogVisible.value = false
getDataList()
} catch (error: any) {
// 处理业务错误
message.error(error.msg)
} finally {
submitLoading.value = false
}
}
})
}
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true;
try {
if (form.id) {
await putObj(form);
message.success('修改成功');
} else {
await addObj(form);
message.success('添加成功');
}
dialogVisible.value = false;
getDataList();
} catch (error: any) {
// 处理业务错误
message.error(error.msg);
} finally {
submitLoading.value = false;
}
}
});
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@@ -1,51 +1,50 @@
<template> <template>
<div> <div>
<multi-upload <multi-upload
ref="commonUploadPicRef" ref="commonUploadPicRef"
@pushListData="pushListData" @pushListData="pushListData"
@delListData="delListData" @delListData="delListData"
:params="params" :params="params"
:fileList="fileList" :fileList="fileList"
:limitNums="limitNums" :limitNums="limitNums"
:accept-file="acceptFile" :accept-file="acceptFile"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineAsyncComponent } from 'vue'; import { ref, defineAsyncComponent } from 'vue';
const multiUpload = defineAsyncComponent(() => import('./multiUpload.vue')) const multiUpload = defineAsyncComponent(() => import('./multiUpload.vue'));
// Props // Props
const props = defineProps<{ const props = defineProps<{
fileList: any[] fileList: any[];
type?: string type?: string;
params?: any params?: any;
limitNums?: number limitNums?: number;
acceptFile?: string acceptFile?: string;
}>() }>();
// 组件引用 // 组件引用
const commonUploadPicRef = ref() const commonUploadPicRef = ref();
// 推送列表数据 // 推送列表数据
const pushListData = (data: { name: string; url: string }) => { const pushListData = (data: { name: string; url: string }) => {
props.fileList.push(data) props.fileList.push(data);
} };
// 删除列表数据 // 删除列表数据
const delListData = (file: string) => { const delListData = (file: string) => {
const index = props.fileList.findIndex((item: any) => item.url === file) const index = props.fileList.findIndex((item: any) => item.url === file);
if (index > -1) { if (index > -1) {
props.fileList.splice(index, 1) props.fileList.splice(index, 1);
} }
} };
// 暴露方法 // 暴露方法
defineExpose({ defineExpose({
commonUploadPic: commonUploadPicRef commonUploadPic: commonUploadPicRef,
}) });
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@@ -49,36 +49,36 @@ const visible = ref(false);
const headers = computed(() => { const headers = computed(() => {
return { return {
Authorization: 'Bearer ' + Session.getToken(), Authorization: 'Bearer ' + Session.getToken(),
TENANT_ID: Session.getTenant() TENANT_ID: Session.getTenant(),
}; };
}); });
const uploadUrl = ref('') const uploadUrl = ref('');
const currentType = ref('') const currentType = ref('');
const uploadRef = ref<{ clearFiles?: () => void }>() const uploadRef = ref<{ clearFiles?: () => void }>();
const titleMap: Record<string, string> = { const titleMap: Record<string, string> = {
titleRelation: '职称信息导入', titleRelation: '职称信息导入',
quaRelation: '职业资格信息导入', quaRelation: '职业资格信息导入',
cerRelation: '教师资格证信息导入', cerRelation: '教师资格证信息导入',
eduDegree: '学历学位信息导入', eduDegree: '学历学位信息导入',
partyChange: '党组织变动信息导入', partyChange: '党组织变动信息导入',
honor: '综合表彰信息导入' honor: '综合表彰信息导入',
} };
// 方法 // 方法
const init = (type: any) => { const init = (type: any) => {
currentType.value = type currentType.value = type;
uploadUrl.value = '/professional/file/importTeacherOtherInfo?type=' + type uploadUrl.value = '/professional/file/importTeacherOtherInfo?type=' + type;
title.value = titleMap[type] || '信息导入' title.value = titleMap[type] || '信息导入';
visible.value = true visible.value = true;
nextTick(() => { nextTick(() => {
uploadRef.value?.clearFiles() uploadRef.value?.clearFiles();
}) });
} };
// Emits // Emits
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'refreshData'): void (e: 'refreshData'): void;
}>() }>();
const handleUploadSuccess = () => { const handleUploadSuccess = () => {
visible.value = false; visible.value = false;
@@ -88,7 +88,7 @@ const handleUploadSuccess = () => {
type: 'success', type: 'success',
}); });
emit('refreshData') emit('refreshData');
}; };
const handleAvatarError = (err: any) => { const handleAvatarError = (err: any) => {
@@ -103,8 +103,8 @@ const handleAvatarError = (err: any) => {
}; };
const handleDownloadTemplate = () => { const handleDownloadTemplate = () => {
downBlobFile('/professional/file/exportTeacherInfoTemplate', { type: currentType.value || 'titleRelation' }, title.value+'模板.xlsx') downBlobFile('/professional/file/exportTeacherInfoTemplate', { type: currentType.value || 'titleRelation' }, title.value + '模板.xlsx');
} };
// 暴露方法给父组件 // 暴露方法给父组件
defineExpose({ defineExpose({

View File

@@ -1,29 +1,28 @@
<template> <template>
<div> <div>
<el-button type="success" icon="view" size="small" @click="handlePdfPreview">预览</el-button> <el-button type="success" icon="view" size="small" @click="handlePdfPreview">预览</el-button>
<el-dialog v-model="visible" width="90%" append-to-body destroy-on-close> <el-dialog v-model="visible" width="90%" append-to-body destroy-on-close>
<auth-img :authSrc="url" style="height:1000px;" v-if="visible" /> <auth-img :authSrc="url" style="height: 1000px" v-if="visible" />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineAsyncComponent } from 'vue' import { ref, defineAsyncComponent } from 'vue';
const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue')) const authImg = defineAsyncComponent(() => import('/@/components/tools/auth-img.vue'));
// Props // Props
defineProps<{ defineProps<{
url: string url: string;
}>() }>();
// 对话框显示状态 // 对话框显示状态
const visible = ref(false) const visible = ref(false);
// 预览PDF // 预览PDF
const handlePdfPreview = () => { const handlePdfPreview = () => {
visible.value = true visible.value = true;
} };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@@ -1,115 +1,114 @@
<template> <template>
<div> <div>
<el-upload <el-upload
class="upload-demo" class="upload-demo"
action="/professional/file/teacherAboutInfoUpload" action="/professional/file/teacherAboutInfoUpload"
:data="params" :data="params"
:headers="headers" :headers="headers"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:on-remove="handleRemove" :on-remove="handleRemove"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:file-list="fileList" :file-list="fileList"
:accept="realAcceptFile" :accept="realAcceptFile"
:limit="realLimitNums" :limit="realLimitNums"
list-type="picture"> list-type="picture"
<el-button size="small" type="primary">点击上传</el-button> >
<el-tag>仅支持{{realAcceptFile}}后缀的文件上传,且文件大小不能超过5M!最大上传数量限制为{{realLimitNums}}.</el-tag> <el-button size="small" type="primary">点击上传</el-button>
</el-upload> <el-tag>仅支持{{ realAcceptFile }}后缀的文件上传,且文件大小不能超过5M!最大上传数量限制为{{ realLimitNums }}.</el-tag>
</div> </el-upload>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue';
import { useMessage } from '/@/hooks/message' import { useMessage } from '/@/hooks/message';
import { Session } from '/@/utils/storage' import { Session } from '/@/utils/storage';
// Props // Props
const props = defineProps<{ const props = defineProps<{
params?: any params?: any;
fileList?: any[] fileList?: any[];
limitNums?: number limitNums?: number;
acceptFile?: string acceptFile?: string;
}>() }>();
// Emits // Emits
const emit = defineEmits<{ const emit = defineEmits<{
delListData: [url: string] delListData: [url: string];
pushListData: [obj: { name: string; url: string }] pushListData: [obj: { name: string; url: string }];
}>() }>();
// 消息提示 hooks // 消息提示 hooks
const message = useMessage() const message = useMessage();
// 响应式数据 // 响应式数据
const realLimitNums = ref(0) const realLimitNums = ref(0);
const realAcceptFile = ref('.jpg,.jpeg,.png,.pdf') const realAcceptFile = ref('.jpg,.jpeg,.png,.pdf');
const fileName = ref('') const fileName = ref('');
// Computed // Computed
const headers = computed(() => { const headers = computed(() => {
return { return {
"Authorization": 'Bearer ' + Session.getToken() Authorization: 'Bearer ' + Session.getToken(),
} };
}) });
// 生命周期 // 生命周期
onMounted(() => { onMounted(() => {
if (props.limitNums) { if (props.limitNums) {
realLimitNums.value = props.limitNums realLimitNums.value = props.limitNums;
} else { } else {
realLimitNums.value = 5 realLimitNums.value = 5;
} }
if (props.acceptFile) { if (props.acceptFile) {
realAcceptFile.value = props.acceptFile realAcceptFile.value = props.acceptFile;
} else { } else {
realAcceptFile.value = ".jpg,.jpeg,.png,.pdf" realAcceptFile.value = '.jpg,.jpeg,.png,.pdf';
} }
}) });
// 方法 // 方法
// const handleUploadLimit = () => { // const handleUploadLimit = () => {
// message.error('最多只能上传1张图片') // message.error('最多只能上传1张图片')
// } // }
const beforeUpload = (file: any) => { const beforeUpload = (file: any) => {
let fileNameStr = file.name let fileNameStr = file.name;
const pos = fileNameStr.lastIndexOf(".") const pos = fileNameStr.lastIndexOf('.');
fileNameStr = fileNameStr.substring(pos + 1, fileNameStr.length) fileNameStr = fileNameStr.substring(pos + 1, fileNameStr.length);
fileNameStr = fileNameStr.toLowerCase() fileNameStr = fileNameStr.toLowerCase();
const extension = fileNameStr === 'jpg' const extension = fileNameStr === 'jpg';
const extension2 = fileNameStr === 'png' const extension2 = fileNameStr === 'png';
const extension3 = fileNameStr === 'jpeg' const extension3 = fileNameStr === 'jpeg';
const extension4 = fileNameStr === 'bmp' const extension4 = fileNameStr === 'bmp';
const extension5 = fileNameStr === 'gif' const extension5 = fileNameStr === 'gif';
const extension6 = fileNameStr === 'pdf' const extension6 = fileNameStr === 'pdf';
const isLt2M = file.size / 1024 / 1024 < 5 const isLt2M = file.size / 1024 / 1024 < 5;
if (!extension && !extension2 && !extension3 && !extension4 && !extension5 && !extension6) { if (!extension && !extension2 && !extension3 && !extension4 && !extension5 && !extension6) {
message.warning('请检查文件上传格式!') message.warning('请检查文件上传格式!');
return false return false;
} }
if (!isLt2M) { if (!isLt2M) {
message.warning('上传图片大小不能超过 5MB!') message.warning('上传图片大小不能超过 5MB!');
return false return false;
} }
fileName.value = file.name fileName.value = file.name;
return true // 返回false不会自动上传 return true; // 返回false不会自动上传
} };
const handleRemove = (file: any) => { const handleRemove = (file: any) => {
emit("delListData", file.url) emit('delListData', file.url);
} };
// const handleUploadError = () => { // const handleUploadError = () => {
// message.error('文件上传失败,请检查文件上传格式与大小') // message.error('文件上传失败,请检查文件上传格式与大小')
// } // }
const handleUploadSuccess = (res: any) => { const handleUploadSuccess = (res: any) => {
message.success('文件上传成功') message.success('文件上传成功');
const obj = { name: res.data.name, url: res.data.url } const obj = { name: res.data.name, url: res.data.url };
emit("pushListData", obj) emit('pushListData', obj);
} };
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@@ -1,158 +1,151 @@
<template> <template>
<el-dialog v-model="visible" title="驳回" width="600px" :close-on-click-modal="false" destroy-on-close> <el-dialog v-model="visible" title="驳回" width="600px" :close-on-click-modal="false" destroy-on-close>
<el-form label-width="100px"> <el-form label-width="100px">
<el-form-item label="姓名 / 工号:"> <el-form-item label="姓名 / 工号:">
<el-tag>{{ dataForm.name }} - {{ dataForm.teacherNo }}</el-tag> <el-tag>{{ dataForm.name }} - {{ dataForm.teacherNo }}</el-tag>
</el-form-item> </el-form-item>
<el-form-item label="类型:"> <el-form-item label="类型:">
<el-input v-model="typeName" disabled /> <el-input v-model="typeName" disabled />
</el-form-item> </el-form-item>
<el-form-item label="驳回理由"> <el-form-item label="驳回理由">
<el-input <el-input type="textarea" v-model="dataForm.backReason" :rows="3" placeholder="请输入驳回理由" maxlength="500" show-word-limit />
type="textarea" </el-form-item>
v-model="dataForm.backReason" </el-form>
:rows="3"
placeholder="请输入驳回理由"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="visible = false">取消</el-button> <el-button @click="visible = false">取消</el-button>
<el-button @click="saveData" :loading="loading" type="primary">保存</el-button> <el-button @click="saveData" :loading="loading" type="primary">保存</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue';
import { useMessage, useMessageBox } from '/@/hooks/message' import { useMessage, useMessageBox } from '/@/hooks/message';
import { examObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation' import { examObj as editTitle } from '/@/api/professional/professionaluser/professionaltitlerelation';
import { examObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation' import { examObj as editQua } from '/@/api/professional/professionaluser/professionalqualificationrelation';
import { examObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation' import { examObj as editCer } from '/@/api/professional/professionaluser/professionalteachercertificaterelation';
import { examObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation' import { examObj as editEducation } from '/@/api/professional/professionaluser/professionalteacheracademicrelation';
import { examObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor' import { examObj as editHonor } from '/@/api/professional/professionaluser/professionalteacherhonor';
import { examObj as editPaper } from '/@/api/professional/professionalteacherpaper' import { examObj as editPaper } from '/@/api/professional/professionalteacherpaper';
import { examObj as editMaterial } from '/@/api/professional/professionalteachingmaterial' import { examObj as editMaterial } from '/@/api/professional/professionalteachingmaterial';
import { examObj as editTopic } from '/@/api/professional/professionaltopiclist' import { examObj as editTopic } from '/@/api/professional/professionaltopiclist';
// Emits // Emits
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'refreshData'): void (e: 'refreshData'): void;
}>() }>();
// 消息提示 // 消息提示
const message = useMessage() const message = useMessage();
const messageBox = useMessageBox() const messageBox = useMessageBox();
// 对话框显示状态 // 对话框显示状态
const visible = ref(false) const visible = ref(false);
// 加载状态 // 加载状态
const loading = ref(false) const loading = ref(false);
// 表单数据 // 表单数据
const dataForm = reactive({ const dataForm = reactive({
name: '', name: '',
id: null as string | number | null, id: null as string | number | null,
backReason: '', backReason: '',
teacherNo: '' teacherNo: '',
}) });
// 类型名称 // 类型名称
const typeName = ref('') const typeName = ref('');
// 类型 // 类型
const type = ref('') const type = ref('');
// 类型映射 // 类型映射
const typeMap: Record<string, string> = { const typeMap: Record<string, string> = {
title: '职称', title: '职称',
qua: '职业资格', qua: '职业资格',
cer: '教师资格证', cer: '教师资格证',
education: '学历学位', education: '学历学位',
honor: '综合表彰', honor: '综合表彰',
paper: '教师论文', paper: '教师论文',
material: '教材', material: '教材',
topic: '课题' topic: '课题',
} };
// API 方法映射 // API 方法映射
const apiMethodMap: Record<string, any> = { const apiMethodMap: Record<string, any> = {
title: editTitle, title: editTitle,
qua: editQua, qua: editQua,
cer: editCer, cer: editCer,
education: editEducation, education: editEducation,
honor: editHonor, honor: editHonor,
paper: editPaper, paper: editPaper,
material: editMaterial, material: editMaterial,
topic: editTopic topic: editTopic,
} };
// 初始化 // 初始化
const init = (row: any, typeValue: string) => { const init = (row: any, typeValue: string) => {
type.value = typeValue type.value = typeValue;
typeName.value = typeMap[typeValue] || '' typeName.value = typeMap[typeValue] || '';
dataForm.name = row.realName || '' dataForm.name = row.realName || '';
dataForm.teacherNo = row.teacherNo || '' dataForm.teacherNo = row.teacherNo || '';
dataForm.id = row.id || null dataForm.id = row.id || null;
dataForm.backReason = '' dataForm.backReason = '';
visible.value = true visible.value = true;
} };
// 保存数据 // 保存数据
const saveData = async () => { const saveData = async () => {
if (!dataForm.backReason.trim()) { if (!dataForm.backReason.trim()) {
message.warning('请填写驳回理由') message.warning('请填写驳回理由');
return return;
} }
const apiMethod = apiMethodMap[type.value] const apiMethod = apiMethodMap[type.value];
if (!apiMethod) { if (!apiMethod) {
message.error('未知的类型') message.error('未知的类型');
return return;
} }
await saveDataMethod(apiMethod) await saveDataMethod(apiMethod);
} };
// 保存数据方法 // 保存数据方法
const saveDataMethod = async (method: any) => { const saveDataMethod = async (method: any) => {
const data = { const data = {
id: dataForm.id, id: dataForm.id,
state: '-2', state: '-2',
backReason: dataForm.backReason backReason: dataForm.backReason,
} };
try { try {
await messageBox.confirm(`确认驳回, ${dataForm.name}${typeName.value}申请?`) await messageBox.confirm(`确认驳回, ${dataForm.name}${typeName.value}申请?`);
loading.value = true loading.value = true;
await method(data) await method(data);
message.success('操作成功') message.success('操作成功');
visible.value = false visible.value = false;
emit('refreshData') emit('refreshData');
} catch (error: any) { } catch (error: any) {
// 用户取消或请求失败 // 用户取消或请求失败
if (error !== 'cancel') { if (error !== 'cancel') {
message.error(error?.msg || '操作失败') message.error(error?.msg || '操作失败');
} }
} finally { } finally {
loading.value = false loading.value = false;
} }
} };
// 暴露方法 // 暴露方法
defineExpose({ defineExpose({
init init,
}) });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.dialog-footer { .dialog-footer {
text-align: right; text-align: right;
} }
</style> </style>

Some files were not shown because too many files have changed in this diff Show More