| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FileType } from 'ant-design-vue/es/upload/interface'; |
| | | |
| | | import type { AgentConversationApi } from '#/api/agent/conversation'; |
| | | |
| | | import { computed, onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | agentImportErrorMessage, |
| | | createAgentConversation, |
| | | downloadAgentReport, |
| | | getAgentCapabilities, |
| | | getAgentConversation, |
| | | listAgentConversations, |
| | | sendAgentAction, |
| | | sendAgentMessage, |
| | | uploadAgentAttachment, |
| | | } from '#/api/agent/conversation'; |
| | | |
| | | defineOptions({ name: 'AgentDataEntry' }); |
| | | |
| | | const MAX_BYTES = 8 * 1024 * 1024; |
| | | const ACCEPT = '.csv,.xlsx,.docx,.pdf'; |
| | | |
| | | const modules = ref<AgentConversationApi.ModuleSpec[]>([]); |
| | | const conversations = ref<AgentConversationApi.ConversationSummary[]>([]); |
| | | const active = ref<AgentConversationApi.Conversation>(); |
| | | const loading = ref(false); |
| | | const busy = ref(false); |
| | | const creating = ref(false); |
| | | const newOpen = ref(false); |
| | | const newModule = ref(''); |
| | | const newText = ref(''); |
| | | const newFile = ref<File>(); |
| | | const newFileName = ref(''); |
| | | const interruptFile = ref<File>(); |
| | | const mappingDraft = ref<Record<string, string>>({}); |
| | | const reviseText = ref(''); |
| | | const syncMes = ref(true); |
| | | |
| | | const interrupt = computed(() => active.value?.interrupts?.at(-1)); |
| | | const card = computed(() => interrupt.value?.card); |
| | | const actions = computed(() => card.value?.actions ?? []); |
| | | const isGatewayModule = computed(() => active.value?.module_id === 'mdm.item'); |
| | | |
| | | const moduleOptions = computed(() => [ |
| | | { label: 'èªå¨è¯å«', value: '' }, |
| | | ...modules.value.map((item) => ({ label: item.title, value: item.module_id })), |
| | | ]); |
| | | |
| | | watch( |
| | | () => interrupt.value?.interrupt_id, |
| | | () => { |
| | | reviseText.value = ''; |
| | | mappingDraft.value = {}; |
| | | interruptFile.value = undefined; |
| | | const value = card.value?.sync_mes; |
| | | syncMes.value = |
| | | typeof value === 'boolean' |
| | | ? value |
| | | : (active.value?.sync_mes ?? active.value?.mes_sync_default ?? true); |
| | | }, |
| | | ); |
| | | |
| | | function statusText(status?: string) { |
| | | const map: Record<string, string> = { |
| | | SUCCEEDED: '导å
¥æå', |
| | | PARTIAL_FAILED: 'é¨å失败', |
| | | FAILED: '失败', |
| | | UNKNOWN: 'ç»ææªç¥', |
| | | RUNNING: 'æäº¤ä¸', |
| | | attached: 'å·²ä¸ä¼ ', |
| | | blocked: '已黿', |
| | | cancelled: '已忶', |
| | | classified: 'å·²è¯å«åºæ¯', |
| | | commit_disabled: 'æªæäº¤', |
| | | confirmed: '已确认', |
| | | created: 'å¾
ä¸ä¼ æä»¶', |
| | | error: 'åºé', |
| | | failed: '失败', |
| | | mapping_applied: 'æ å°å·²æ´æ°', |
| | | needs_review: 'å¾
确认', |
| | | parsed: '已解æ', |
| | | preview_ready: 'å¾
确认', |
| | | revise: 'å¾
éæ°æ ¡éª', |
| | | stale: 'å·²è¿æï¼é鿣', |
| | | validated: 'å¾
确认', |
| | | }; |
| | | return map[status ?? ''] ?? status ?? 'è¿è¡ä¸'; |
| | | } |
| | | |
| | | function statusColor(status?: string) { |
| | | if (status === 'SUCCEEDED' || status === 'confirmed') return 'green'; |
| | | if ( |
| | | status === 'PARTIAL_FAILED' || |
| | | status === 'revise' || |
| | | status === 'stale' |
| | | ) { |
| | | return 'orange'; |
| | | } |
| | | if ( |
| | | status === 'FAILED' || |
| | | status === 'failed' || |
| | | status === 'error' || |
| | | status === 'blocked' || |
| | | status === 'cancelled' |
| | | ) { |
| | | return 'red'; |
| | | } |
| | | return 'blue'; |
| | | } |
| | | |
| | | function checkFile(raw: File) { |
| | | const name = raw.name.toLowerCase(); |
| | | if (!ACCEPT.split(',').some((ext) => name.endsWith(ext))) { |
| | | message.error('ä»
æ¯æ CSVãXLSXãDOCX æ PDF æä»¶'); |
| | | return false; |
| | | } |
| | | if (raw.size > MAX_BYTES) { |
| | | message.error('æä»¶ä¸è½è¶
è¿ 8 MiB'); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | function beforeNewUpload(fileInfo: FileType) { |
| | | const raw = fileInfo as unknown as File; |
| | | if (!checkFile(raw)) return false; |
| | | newFile.value = raw; |
| | | newFileName.value = raw.name; |
| | | return false; |
| | | } |
| | | |
| | | function beforeInterruptUpload(fileInfo: FileType) { |
| | | const raw = fileInfo as unknown as File; |
| | | if (!checkFile(raw)) return false; |
| | | interruptFile.value = raw; |
| | | return false; |
| | | } |
| | | |
| | | async function boot() { |
| | | loading.value = true; |
| | | try { |
| | | const [capabilities, list] = await Promise.all([ |
| | | getAgentCapabilities(), |
| | | listAgentConversations({ page_size: 50 }), |
| | | ]); |
| | | modules.value = capabilities.modules ?? []; |
| | | conversations.value = list.items ?? []; |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function reloadConversations() { |
| | | try { |
| | | const list = await listAgentConversations({ page_size: 50 }); |
| | | conversations.value = list.items ?? []; |
| | | } catch { |
| | | // åè¡¨å·æ°å¤±è´¥ä¸é»å¡ä¸»æµç¨ |
| | | } |
| | | } |
| | | |
| | | async function openConversation(conversationId: string) { |
| | | if (busy.value) return; |
| | | busy.value = true; |
| | | try { |
| | | active.value = await getAgentConversation(conversationId); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | busy.value = false; |
| | | } |
| | | } |
| | | |
| | | async function createConversation() { |
| | | if (!newFile.value && !newText.value.trim()) { |
| | | message.warning('请ä¸ä¼ æä»¶æå¡«åè¡¥å½è¯´æ'); |
| | | return; |
| | | } |
| | | creating.value = true; |
| | | try { |
| | | let attachmentId: number | undefined; |
| | | if (newFile.value) { |
| | | attachmentId = await uploadAgentAttachment(newFile.value); |
| | | } |
| | | active.value = await createAgentConversation({ |
| | | attachment_id: attachmentId ? String(attachmentId) : undefined, |
| | | module_id: newModule.value || undefined, |
| | | text: newText.value.trim() || undefined, |
| | | title: newFile.value?.name || newText.value.trim().slice(0, 60) || undefined, |
| | | }); |
| | | newOpen.value = false; |
| | | newFile.value = undefined; |
| | | newFileName.value = ''; |
| | | newText.value = ''; |
| | | await reloadConversations(); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | creating.value = false; |
| | | } |
| | | } |
| | | |
| | | async function apply(result: AgentConversationApi.Conversation, tip?: string) { |
| | | active.value = result; |
| | | if (tip) message.success(tip); |
| | | await reloadConversations(); |
| | | } |
| | | |
| | | async function act( |
| | | action: string, |
| | | payload: Record<string, unknown> = {}, |
| | | tip?: string, |
| | | ) { |
| | | const current = active.value; |
| | | const pending = interrupt.value; |
| | | if (!current || !pending) return; |
| | | busy.value = true; |
| | | try { |
| | | const result = await sendAgentAction(current.conversation_id, { |
| | | action, |
| | | interrupt_id: pending.interrupt_id, |
| | | payload, |
| | | revision: pending.revision, |
| | | }); |
| | | await apply(result, tip); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | busy.value = false; |
| | | } |
| | | } |
| | | |
| | | function chooseModule(moduleId: string) { |
| | | return act('choose_scenario', { module_id: moduleId }, '已鿩ä¸å¡åºæ¯'); |
| | | } |
| | | |
| | | async function confirmMapping() { |
| | | const missing = card.value?.missing_fields ?? []; |
| | | const mapping: Record<string, string> = {}; |
| | | for (const field of missing) { |
| | | const column = mappingDraft.value[field.name]; |
| | | if (!column) { |
| | | message.warning(`请为ã${field.label}ã鿩坹åºå`); |
| | | return; |
| | | } |
| | | mapping[field.name] = column; |
| | | } |
| | | await act('confirm_mapping', { mapping }, 'æ å°å·²æäº¤ï¼éæ°æ ¡éª'); |
| | | } |
| | | |
| | | async function uploadForFileInterrupt() { |
| | | const current = active.value; |
| | | if (!current) return; |
| | | if (!interruptFile.value) { |
| | | message.warning('请å
éæ©æä»¶'); |
| | | return; |
| | | } |
| | | busy.value = true; |
| | | try { |
| | | const attachmentId = await uploadAgentAttachment(interruptFile.value); |
| | | const result = await sendAgentMessage(current.conversation_id, { |
| | | attachment_id: String(attachmentId), |
| | | text: 'å·²ä¸ä¼ è¦è¡¥å½çæä»¶ã', |
| | | }); |
| | | interruptFile.value = undefined; |
| | | await apply(result); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | busy.value = false; |
| | | } |
| | | } |
| | | |
| | | async function revise() { |
| | | const text = reviseText.value.trim(); |
| | | const payload: Record<string, unknown> = {}; |
| | | if (text) payload.text = text; |
| | | if (isGatewayModule.value) payload.sync_mes = syncMes.value; |
| | | if (!text && !isGatewayModule.value) { |
| | | message.warning('请è¾å
¥ä¿®è®¢å
容ï¼ä¾å¦â第2è¡ åç§° æ¹ä¸º XXXâ'); |
| | | return; |
| | | } |
| | | await act('revise', payload, 'å·²æäº¤ä¿®è®¢ï¼éæ°æ ¡éª'); |
| | | reviseText.value = ''; |
| | | } |
| | | |
| | | async function toggleSync(value: boolean) { |
| | | syncMes.value = value; |
| | | await act('revise', { sync_mes: value }, 'å·²æ´æ°åæ¥é项ï¼éæ°æ ¡éª'); |
| | | } |
| | | |
| | | function confirmImport() { |
| | | return act('confirm', {}, 'å·²ç¡®è®¤ï¼æ£å¨æäº¤'); |
| | | } |
| | | |
| | | function cancelConversation() { |
| | | return act('cancel', {}, 'å·²åæ¶æ¬æ¬¡è¡¥å½'); |
| | | } |
| | | |
| | | async function downloadReport() { |
| | | const current = active.value; |
| | | if (!current) return; |
| | | try { |
| | | const response = await downloadAgentReport(current.conversation_id); |
| | | const disposition = String( |
| | | response.headers?.['content-disposition'] ?? '', |
| | | ); |
| | | const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition); |
| | | downloadFileFromBlobPart({ |
| | | fileName: match?.[1] || `${current.title || 'è¡¥å½åæ§'}.xlsx`, |
| | | source: response.data, |
| | | }); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } |
| | | } |
| | | |
| | | onMounted(boot); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <div class="flex h-full gap-3 p-3"> |
| | | <aside |
| | | class="flex w-64 shrink-0 flex-col overflow-hidden rounded-lg border border-border bg-card" |
| | | > |
| | | <div class="border-b border-border p-3"> |
| | | <a-button type="primary" block @click="newOpen = true"> |
| | | æ°å»ºè¡¥å½ |
| | | </a-button> |
| | | <div class="mt-2 text-xs text-gray-500"> |
| | | ä¸ä¼ æä»¶åèªå¨è¯å«ä¸å¡åºæ¯ï¼ç¡®è®¤åä¼å®æ´æ ¡éªã |
| | | </div> |
| | | </div> |
| | | <div class="flex-1 overflow-y-auto p-2"> |
| | | <a-spin :spinning="loading"> |
| | | <a-empty |
| | | v-if="!conversations.length" |
| | | description="ææ è¡¥å½ä¼è¯" |
| | | /> |
| | | <button |
| | | v-for="item in conversations" |
| | | :key="item.conversation_id" |
| | | class="mb-1 w-full rounded-md p-2 text-left transition-colors hover:bg-gray-100 dark:hover:bg-gray-800" |
| | | :class="{ |
| | | 'bg-blue-50 dark:bg-blue-900/30': |
| | | item.conversation_id === active?.conversation_id, |
| | | }" |
| | | type="button" |
| | | @click="openConversation(item.conversation_id)" |
| | | > |
| | | <div class="truncate text-sm font-medium"> |
| | | {{ item.title || 'æªå½åè¡¥å½' }} |
| | | </div> |
| | | <div class="mt-1 flex items-center gap-2 text-xs text-gray-500"> |
| | | <a-tag :color="statusColor(item.status)" class="!mr-0"> |
| | | {{ statusText(item.status) }} |
| | | </a-tag> |
| | | <span class="truncate"> |
| | | {{ item.module_title || 'å¾
è¯å«' }} |
| | | </span> |
| | | </div> |
| | | </button> |
| | | </a-spin> |
| | | </div> |
| | | </aside> |
| | | |
| | | <section |
| | | class="flex min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-border bg-card" |
| | | > |
| | | <header |
| | | class="flex items-center justify-between border-b border-border px-4 py-3" |
| | | > |
| | | <div class="min-w-0"> |
| | | <div class="truncate text-base font-semibold"> |
| | | {{ active?.title || 'æ¨¡åæ°æ®è¡¥å½' }} |
| | | </div> |
| | | <div class="mt-1 flex items-center gap-2 text-xs text-gray-500"> |
| | | <a-tag :color="statusColor(active?.status)" class="!mr-0"> |
| | | {{ statusText(active?.status) }} |
| | | </a-tag> |
| | | <span v-if="active?.module_title">{{ active.module_title }}</span> |
| | | <span v-if="active?.total">å
± {{ active.total }} è¡</span> |
| | | </div> |
| | | </div> |
| | | <a-button |
| | | v-if="active?.receipt?.command_id" |
| | | :disabled="busy" |
| | | @click="downloadReport" |
| | | > |
| | | ä¸è½½åæ§ |
| | | </a-button> |
| | | </header> |
| | | |
| | | <div class="flex-1 overflow-y-auto p-4"> |
| | | <a-empty |
| | | v-if="!active" |
| | | description="ä»å·¦ä¾§éæ©ä¼è¯ï¼æç¹å»ãæ°å»ºè¡¥å½ãå¼å§" |
| | | /> |
| | | <div v-else class="space-y-3"> |
| | | <div |
| | | v-for="item in active.messages" |
| | | :key="item.message_id" |
| | | class="flex" |
| | | :class="item.role === 'user' ? 'justify-end' : 'justify-start'" |
| | | > |
| | | <div |
| | | class="max-w-[80%] rounded-lg px-3 py-2 text-sm" |
| | | :class=" |
| | | item.role === 'user' |
| | | ? 'bg-blue-500 text-white' |
| | | : 'bg-gray-100 dark:bg-gray-800' |
| | | " |
| | | > |
| | | <div class="whitespace-pre-wrap">{{ item.text }}</div> |
| | | <div |
| | | v-for="(msgCard, index) in item.cards || []" |
| | | :key="`${item.message_id}-${index}`" |
| | | class="mt-2 rounded-md border border-border bg-card p-2 text-gray-800 dark:text-gray-100" |
| | | > |
| | | <template v-if="msgCard.kind === 'receipt'"> |
| | | <div class="mb-1 font-semibold"> |
| | | {{ msgCard.title || '导å
¥åæ§' }} |
| | | </div> |
| | | <div class="text-xs text-gray-500"> |
| | | æå {{ msgCard.summary?.success ?? 0 }}ï¼è·³è¿ |
| | | {{ msgCard.summary?.skipped ?? 0 }}ï¼å¤±è´¥ |
| | | {{ msgCard.summary?.failed ?? 0 }} |
| | | </div> |
| | | <a-table |
| | | class="mt-2" |
| | | :columns="[ |
| | | { title: 'ç¼ç ', dataIndex: 'code', width: 160 }, |
| | | { title: 'åç§°', dataIndex: 'name' }, |
| | | { title: 'ç»æ', dataIndex: 'status', width: 90 }, |
| | | ]" |
| | | :data-source="msgCard.rows || []" |
| | | :pagination="false" |
| | | row-key="candidate_id" |
| | | size="small" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <a-tag |
| | | v-if="column.dataIndex === 'status'" |
| | | :color="record.status === 'SUCCESS' ? 'green' : 'red'" |
| | | > |
| | | {{ record.status }} |
| | | </a-tag> |
| | | </template> |
| | | </a-table> |
| | | <a-button |
| | | class="mt-2" |
| | | size="small" |
| | | type="link" |
| | | @click="downloadReport" |
| | | > |
| | | ä¸è½½æ¥è¡¨ |
| | | </a-button> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <footer v-if="active && interrupt" class="border-t border-border p-3"> |
| | | <a-spin :spinning="busy"> |
| | | <!-- åºæ¯éæ© --> |
| | | <div v-if="interrupt.kind === 'scenario'"> |
| | | <div class="mb-2 font-medium">{{ card?.title }}</div> |
| | | <div class="mb-2 text-xs text-gray-500">{{ card?.body }}</div> |
| | | <div class="flex flex-wrap gap-2"> |
| | | <a-button |
| | | v-for="candidate in card?.candidates || []" |
| | | :key="candidate.module_id" |
| | | :disabled="busy" |
| | | @click="chooseModule(candidate.module_id)" |
| | | > |
| | | {{ candidate.title }} |
| | | <a-tag class="ml-1" color="blue"> |
| | | {{ candidate.capability }} |
| | | </a-tag> |
| | | </a-button> |
| | | <a-button |
| | | v-if="actions.includes('cancel')" |
| | | danger |
| | | :disabled="busy" |
| | | @click="cancelConversation" |
| | | > |
| | | åæ¶ |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åæ®µæ å° --> |
| | | <div v-else-if="interrupt.kind === 'mapping'"> |
| | | <div class="mb-2 font-medium">{{ card?.title }}</div> |
| | | <div class="mb-2 text-xs text-gray-500">{{ card?.body }}</div> |
| | | <div class="space-y-2"> |
| | | <div |
| | | v-for="field in card?.missing_fields || []" |
| | | :key="field.name" |
| | | class="flex items-center gap-2" |
| | | > |
| | | <span class="w-28 text-sm">{{ field.label }}</span> |
| | | <a-select |
| | | v-model:value="mappingDraft[field.name]" |
| | | class="min-w-56" |
| | | placeholder="éæ©æä»¶ä¸å¯¹åºçå" |
| | | :options=" |
| | | (card?.columns || []).map((column) => ({ |
| | | label: column, |
| | | value: column, |
| | | })) |
| | | " |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div class="mt-3 flex gap-2"> |
| | | <a-button |
| | | type="primary" |
| | | :loading="busy" |
| | | @click="confirmMapping" |
| | | > |
| | | 确认æ å° |
| | | </a-button> |
| | | <a-button danger :disabled="busy" @click="cancelConversation"> |
| | | åæ¶ |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¥ä¼ æä»¶ --> |
| | | <div v-else-if="interrupt.kind === 'file'"> |
| | | <div class="mb-2 font-medium">{{ card?.title }}</div> |
| | | <div class="mb-2 text-xs text-gray-500">{{ card?.body }}</div> |
| | | <a-upload |
| | | accept=".csv,.xlsx,.docx,.pdf" |
| | | :before-upload="beforeInterruptUpload" |
| | | :max-count="1" |
| | | :show-upload-list="false" |
| | | > |
| | | <a-button>{{ interruptFile ? 'éæ°éæ©' : 'éæ©æä»¶' }}</a-button> |
| | | </a-upload> |
| | | <span v-if="interruptFile" class="ml-2 text-xs text-gray-500"> |
| | | {{ interruptFile.name }} |
| | | </span> |
| | | <div class="mt-3"> |
| | | <a-button |
| | | type="primary" |
| | | :disabled="!interruptFile" |
| | | :loading="busy" |
| | | @click="uploadForFileInterrupt" |
| | | > |
| | | ä¸ä¼ å¹¶ç»§ç» |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ ¡éª / é¢è§ç¡®è®¤ --> |
| | | <div v-else-if="['preview', 'validation'].includes(interrupt.kind)"> |
| | | <div class="mb-2 flex items-center justify-between"> |
| | | <span class="font-medium">{{ card?.title }}</span> |
| | | <span class="text-xs text-gray-500"> |
| | | {{ card?.notice }} |
| | | </span> |
| | | </div> |
| | | |
| | | <a-alert |
| | | v-if="card?.issues?.length" |
| | | class="mb-2" |
| | | type="error" |
| | | show-icon |
| | | > |
| | | <template #message> |
| | | 黿项 {{ card.issues.length }} æ¡ï¼é修订åéæ°æ ¡éª |
| | | </template> |
| | | <ul class="mt-1 list-decimal pl-4 text-xs"> |
| | | <li v-for="(issue, index) in card.issues" :key="index"> |
| | | {{ issue.message }} |
| | | <span v-if="issue.code" class="text-gray-400"> |
| | | ï¼{{ issue.code }}ï¼ |
| | | </span> |
| | | </li> |
| | | </ul> |
| | | </a-alert> |
| | | |
| | | <a-alert |
| | | v-if="card?.warnings?.length" |
| | | class="mb-2" |
| | | type="warning" |
| | | show-icon |
| | | > |
| | | <template #message> |
| | | æç¤º {{ card.warnings.length }} æ¡ï¼ä¸é»ææäº¤ |
| | | </template> |
| | | <ul class="mt-1 list-decimal pl-4 text-xs"> |
| | | <li v-for="(item, index) in card.warnings" :key="index"> |
| | | {{ item.message }} |
| | | </li> |
| | | </ul> |
| | | </a-alert> |
| | | |
| | | <div |
| | | v-if="card?.summary" |
| | | class="mb-2 flex flex-wrap gap-3 text-xs text-gray-600 dark:text-gray-300" |
| | | > |
| | | <span>å
± {{ card.summary.total ?? 0 }} è¡</span> |
| | | <span class="text-green-600"> |
| | | æ°å»º {{ card.summary.create ?? 0 }} |
| | | </span> |
| | | <span class="text-orange-500"> |
| | | è·³è¿ {{ card.summary.skip ?? 0 }} |
| | | </span> |
| | | <span class="text-red-500"> |
| | | 黿 {{ card.summary.blocked ?? 0 }} |
| | | </span> |
| | | </div> |
| | | |
| | | <div v-if="card?.side_effects?.length" class="mb-2 text-xs text-gray-500"> |
| | | 颿影åï¼{{ card.side_effects.join('ï¼') }} |
| | | </div> |
| | | |
| | | <div |
| | | v-if="isGatewayModule" |
| | | class="mb-2 flex items-center gap-2 text-sm" |
| | | > |
| | | <span>åæ¥å° MES</span> |
| | | <a-switch |
| | | :checked="syncMes" |
| | | :disabled="busy" |
| | | @change="toggleSync" |
| | | /> |
| | | <span class="text-xs text-gray-500"> |
| | | å
³éåä»
å建 ERP ç©æ |
| | | </span> |
| | | </div> |
| | | |
| | | <div class="flex gap-2"> |
| | | <a-input |
| | | v-model:value="reviseText" |
| | | class="flex-1" |
| | | placeholder="å¦éä¿®æ¹ï¼è¾å
¥ä¿®è®¢è¯´æï¼ä¾å¦ï¼ç¬¬2è¡ åç§° æ¹ä¸º XXX" |
| | | :disabled="busy" |
| | | @press-enter="revise" |
| | | /> |
| | | <a-button :disabled="busy" @click="revise">ä¿®è®¢å¹¶éæ£</a-button> |
| | | <a-button |
| | | v-if="actions.includes('confirm')" |
| | | type="primary" |
| | | :disabled="busy || card?.can_commit === false" |
| | | :loading="busy" |
| | | @click="confirmImport" |
| | | > |
| | | 确认导å
¥ |
| | | </a-button> |
| | | <a-button |
| | | v-if="actions.includes('cancel')" |
| | | danger |
| | | :disabled="busy" |
| | | @click="cancelConversation" |
| | | > |
| | | åæ¶ |
| | | </a-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <a-alert |
| | | v-else |
| | | type="info" |
| | | show-icon |
| | | :message="card?.title || 'å½åæ¥éª¤éè¦å¤ç'" |
| | | :description="card?.body" |
| | | /> |
| | | </a-spin> |
| | | </footer> |
| | | </section> |
| | | |
| | | <a-modal |
| | | v-model:open="newOpen" |
| | | title="æ°å»ºè¡¥å½" |
| | | :confirm-loading="creating" |
| | | ok-text="å¼å§è¡¥å½" |
| | | cancel-text="åæ¶" |
| | | :width="560" |
| | | @ok="createConversation" |
| | | > |
| | | <div class="space-y-3"> |
| | | <div> |
| | | <div class="mb-1 text-sm">ä¸å¡åºæ¯</div> |
| | | <a-select |
| | | v-model:value="newModule" |
| | | class="w-full" |
| | | :options="moduleOptions" |
| | | /> |
| | | <div class="mt-1 text-xs text-gray-500"> |
| | | ä¸ç¡®å®æ¶å¯ä¿æèªå¨è¯å«ï¼ç³»ç»ä¼æ ¹æ®è¡¨å¤´åè¯´ææ¨èåºæ¯ã |
| | | </div> |
| | | </div> |
| | | <div> |
| | | <div class="mb-1 text-sm">è¡¥å½è¯´æï¼å¯éï¼</div> |
| | | <a-textarea |
| | | v-model:value="newText" |
| | | :rows="2" |
| | | placeholder="ä¾å¦ï¼å¯¼å
¥ä¸æ¹ä¾åºåæ¡£æ¡" |
| | | /> |
| | | </div> |
| | | <div> |
| | | <div class="mb-1 text-sm">æ°æ®æä»¶</div> |
| | | <a-upload |
| | | accept=".csv,.xlsx,.docx,.pdf" |
| | | :before-upload="beforeNewUpload" |
| | | :max-count="1" |
| | | :show-upload-list="false" |
| | | > |
| | | <a-button>éæ©æä»¶</a-button> |
| | | </a-upload> |
| | | <span v-if="newFileName" class="ml-2 text-xs text-gray-500"> |
| | | {{ newFileName }} |
| | | </span> |
| | | <div class="mt-1 text-xs text-gray-500"> |
| | | æ¯æ CSVãXLSXãDOCX ä¸å¯å¤å¶ææ¬ç PDFï¼å个æä»¶ä¸è¶
è¿ 8 |
| | | MiBãæ«æä»¶æä¸æ¯æã |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </a-modal> |
| | | </div> |
| | | </Page> |
| | | </template> |