feat(ai): 添加AI功能总开关及智能体导入功能
- 在环境变量中添加 VITE_APP_AI_ENABLE 开关配置
- 实现 isAiEnable() 钩子函数用于判断AI功能是否启用
- 添加AI模块路由总开关,关闭时整组路由不注册
- 在质检助手、AI建议等组件中添加AI开关条件渲染
- 实现过滤AI菜单功能,AI关闭时隐藏AI模块菜单
- 在工作订单表单中添加AI功能可用性计算
- 添加智能体导入物料功能模块及API接口
- 实现智能体导入的完整流程:解析、校验、审核、提交
- 添加代理API转发配置到智能体服务端口
- 在基础数据、销售报价、采购发票等页面添加AI相关功能开关控制
| | |
| | | # ææ¡£å°åçå¼å
³ |
| | | VITE_APP_DOCALERT_ENABLE=true |
| | | |
| | | # AI åè½æ»å¼å
³ï¼false æ¶éèææ AI å
¥å£ï¼AI 模åèåãOCRã颿µãæºè½å¤å®ãæºè½ä½å¯¼å
¥ï¼ |
| | | VITE_APP_AI_ENABLE=true |
| | | |
| | | # ç¾åº¦ç»è®¡ |
| | | VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093 |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { InternalAxiosRequestConfig } from '@vben/request'; |
| | | |
| | | import { RequestClient } from '@vben/request'; |
| | | import { useAccessStore } from '@vben/stores'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | /** |
| | | * Python æºè½ä½æå¡ä¸å¨ /admin-api ä¸ï¼å¼åæç± Vite 代ç /agent-api 转åå° 127.0.0.1:8091ã |
| | | * 该æå¡ç´æ¥è¿åä¸å¡ JSONï¼ä¸ä½¿ç¨ CommonResult å
è£
ï¼å æ¤åç¬å»ºå®¢æ·ç«¯ã |
| | | */ |
| | | const agentRequestClient = new RequestClient({ |
| | | baseURL: '/agent-api', |
| | | responseReturn: 'body', |
| | | timeout: 120_000, |
| | | }); |
| | | |
| | | agentRequestClient.addRequestInterceptor({ |
| | | fulfilled: (config: InternalAxiosRequestConfig) => { |
| | | const token = useAccessStore().accessToken; |
| | | if (token) { |
| | | config.headers.Authorization = `Bearer ${token}`; |
| | | } |
| | | return config; |
| | | }, |
| | | }); |
| | | |
| | | export namespace MdmAgentImportApi { |
| | | export type RowAction = 'CREATE' | 'SKIP' | 'BLOCKED'; |
| | | export type RowStatus = 'SUCCESS' | 'FAILED' | 'SKIPPED' | 'UNKNOWN'; |
| | | |
| | | export interface Evidence { |
| | | sheet: string; |
| | | row: number; |
| | | column: string; |
| | | raw: string; |
| | | } |
| | | |
| | | export interface Candidate { |
| | | candidate_id: string; |
| | | source_sha256: string; |
| | | values: Record<string, string>; |
| | | evidence: Record<string, Evidence>; |
| | | issues: string[]; |
| | | } |
| | | |
| | | export interface Issue { |
| | | code?: string; |
| | | field?: string; |
| | | message: string; |
| | | retryable?: boolean; |
| | | } |
| | | |
| | | export interface Resolved { |
| | | category_id?: number; |
| | | category_name?: string; |
| | | unit_measure_id?: number; |
| | | unit_measure_name?: string; |
| | | } |
| | | |
| | | export interface PreflightRow { |
| | | candidate_id: string; |
| | | row_no?: number; |
| | | code?: string; |
| | | name?: string; |
| | | action: RowAction; |
| | | resolved?: Resolved; |
| | | blockers?: Issue[]; |
| | | warnings?: Issue[]; |
| | | target_item_id?: number; |
| | | } |
| | | |
| | | export interface Preflight { |
| | | preflight_hash: string; |
| | | source: { |
| | | attachment_id: number; |
| | | session_id?: number; |
| | | source_sha256: string; |
| | | proposal_revision?: string; |
| | | }; |
| | | policy: { |
| | | update_allowed: boolean; |
| | | mes_sync_allowed: boolean; |
| | | max_rows: number; |
| | | }; |
| | | summary: { |
| | | total: number; |
| | | create: number; |
| | | skip: number; |
| | | blocked: number; |
| | | }; |
| | | can_commit: boolean; |
| | | side_effects: string[]; |
| | | rows: PreflightRow[]; |
| | | blocking_items: Issue[]; |
| | | } |
| | | |
| | | export interface Correction { |
| | | candidate_id: string; |
| | | field: 'categoryCode' | 'code' | 'name' | 'specification' | 'unitMeasureCode'; |
| | | value: string; |
| | | reason: string; |
| | | } |
| | | |
| | | export interface ReceiptRow { |
| | | candidate_id: string; |
| | | row_no?: number; |
| | | code?: string; |
| | | name?: string; |
| | | action: 'CREATE' | 'SKIP'; |
| | | status: RowStatus; |
| | | target_item_id?: number; |
| | | target_version?: string; |
| | | error_code?: string; |
| | | error_message?: string; |
| | | retryable?: boolean; |
| | | } |
| | | |
| | | export interface Receipt { |
| | | command_id: string; |
| | | idempotency_key: string; |
| | | status: 'FAILED' | 'PARTIAL_FAILED' | 'RUNNING' | 'SUCCEEDED' | 'UNKNOWN'; |
| | | attachment_id: number; |
| | | session_id?: number; |
| | | source_sha256: string; |
| | | preflight_hash: string; |
| | | proposal_hash?: string; |
| | | summary: { |
| | | total: number; |
| | | success: number; |
| | | failed: number; |
| | | skipped: number; |
| | | unknown: number; |
| | | }; |
| | | error_code?: string; |
| | | error_message?: string; |
| | | rows: ReceiptRow[]; |
| | | create_time?: string; |
| | | completed_time?: string; |
| | | } |
| | | |
| | | export interface ImportState { |
| | | task_id: string; |
| | | attachment_id: string; |
| | | filename: string; |
| | | source_sha256: string; |
| | | candidates: Candidate[]; |
| | | issues: Array<Issue & { candidate_id?: string; candidate_ids?: string[] }>; |
| | | revision: number; |
| | | status: string; |
| | | preflight?: Preflight; |
| | | preflight_hash?: string; |
| | | proposal?: Record<string, unknown>; |
| | | proposal_hash?: string; |
| | | command_id?: string; |
| | | receipt?: Receipt; |
| | | last_error?: string; |
| | | } |
| | | |
| | | export interface ReviewInterrupt { |
| | | type: string; |
| | | revision: number; |
| | | issues: Array<Issue & { candidate_id?: string; candidate_ids?: string[] }>; |
| | | candidates: Candidate[]; |
| | | preflight: { |
| | | hash?: string; |
| | | can_commit: boolean; |
| | | summary?: Preflight['summary']; |
| | | blocking_items: Issue[]; |
| | | needs_attention: PreflightRow[]; |
| | | }; |
| | | last_error?: string; |
| | | notice?: string; |
| | | } |
| | | |
| | | export interface PublicState { |
| | | state: ImportState; |
| | | interrupts: ReviewInterrupt[]; |
| | | } |
| | | |
| | | export interface ReviewReq { |
| | | expected_revision: number; |
| | | action: 'cancel' | 'confirm' | 'revise'; |
| | | corrections?: Correction[]; |
| | | expected_preflight_hash?: string; |
| | | expected_proposal_hash?: string; |
| | | } |
| | | } |
| | | |
| | | /** å建补å½ä¼è¯ï¼Javaï¼ */ |
| | | export function createImportSession() { |
| | | return requestClient.post<number>('/agent/bridge/import-sessions'); |
| | | } |
| | | |
| | | /** ç»å®éä»¶å°è¡¥å½ä¼è¯ï¼è¿å attachmentIdï¼Javaï¼ */ |
| | | export function bindImportAttachment(sessionId: number, blobId: number) { |
| | | return requestClient.post<number>( |
| | | `/agent/bridge/import-sessions/${sessionId}/attachments`, |
| | | { blobId }, |
| | | ); |
| | | } |
| | | |
| | | /** å建æºè½ä½å¯¼å
¥ä»»å¡å¹¶è§¦åè§£æä¸é¢æ£ï¼Pythonï¼ */ |
| | | export function startMaterialImport(attachmentId: number) { |
| | | return agentRequestClient.post<MdmAgentImportApi.PublicState>( |
| | | '/v1/material-imports', |
| | | { attachment_id: String(attachmentId) }, |
| | | ); |
| | | } |
| | | |
| | | /** 读åä»»å¡å½åç¶æï¼Pythonï¼ */ |
| | | export function getMaterialImportTask(taskId: string) { |
| | | return agentRequestClient.get<MdmAgentImportApi.PublicState>( |
| | | `/v1/material-imports/${taskId}`, |
| | | ); |
| | | } |
| | | |
| | | /** 修订æç¡®è®¤ï¼Pythonï¼ */ |
| | | export function reviewMaterialImport( |
| | | taskId: string, |
| | | data: MdmAgentImportApi.ReviewReq, |
| | | ) { |
| | | return agentRequestClient.post<MdmAgentImportApi.PublicState>( |
| | | `/v1/material-imports/${taskId}/review`, |
| | | data, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æäº¤åæ§ï¼Pythonï¼ */ |
| | | export function getMaterialImportReceipt(taskId: string) { |
| | | return agentRequestClient.get<MdmAgentImportApi.Receipt>( |
| | | `/v1/material-imports/${taskId}/receipt`, |
| | | ); |
| | | } |
| | | |
| | | interface AgentErrorDetail { |
| | | error_code?: string; |
| | | message?: string; |
| | | } |
| | | |
| | | /** ä» Python é误ååºä¸æåå¯è¯»ææ¡ */ |
| | | export function agentImportErrorMessage(error: unknown): string { |
| | | const err = error as |
| | | | { data?: unknown; message?: string; response?: { data?: unknown } } |
| | | | undefined; |
| | | const body = (err?.response?.data ?? err?.data) as |
| | | | AgentErrorDetail |
| | | | string |
| | | | undefined; |
| | | const detail = |
| | | typeof body === 'string' |
| | | ? body |
| | | : ((body as { detail?: unknown } | undefined)?.detail ?? body); |
| | | if (typeof detail === 'string') { |
| | | return detail; |
| | | } |
| | | const info = detail as AgentErrorDetail | undefined; |
| | | if (info?.message) { |
| | | return info.error_code |
| | | ? `${info.message}ï¼${info.error_code}ï¼` |
| | | : info.message; |
| | | } |
| | | return err?.message || 'æºè½ä½æå¡è¯·æ±å¤±è´¥'; |
| | | } |
| | |
| | | export function isDocAlertEnable(): boolean { |
| | | return import.meta.env.VITE_APP_DOCALERT_ENABLE !== 'false'; |
| | | } |
| | | |
| | | /** AI åè½æ»å¼å
³ï¼å
³éåéèææ AI å
¥å£ï¼æ¨¡åèåãOCRã颿µãæºè½å¤å®ãæºè½ä½å¯¼å
¥çï¼ */ |
| | | export function isAiEnable(): boolean { |
| | | return import.meta.env.VITE_APP_AI_ENABLE !== 'false'; |
| | | } |
| | |
| | | import type { RouteRecordRaw } from 'vue-router'; |
| | | |
| | | const routes: RouteRecordRaw[] = [ |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | |
| | | /** AI æ»å¼å
³å
³éæ¶æ´ç»è·¯ç±ä¸æ³¨åï¼é¿å
éè¿å°åæ ç´æ¥è®¿é® */ |
| | | const routes: RouteRecordRaw[] = isAiEnable() ? [ |
| | | { |
| | | path: '/ai', |
| | | name: 'Ai', |
| | |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | ] : []; |
| | | |
| | | export default routes; |
| | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { LOGIN_PATH } from '@vben/constants'; |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | import { preferences } from '@vben/preferences'; |
| | | import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; |
| | | |
| | |
| | | } from '#/api'; |
| | | import { $t } from '#/locales'; |
| | | import { automaticApproval, loadAuditPolicy } from '#/store/audit-policy'; |
| | | |
| | | /** AI æ»å¼å
³å
³éæ¶ï¼è¿å /ai 模åèåä¸å¹¶éè */ |
| | | function filterAiMenus<T extends { children?: T[]; path?: string }>( |
| | | menus: T[], |
| | | ): T[] { |
| | | return menus |
| | | .filter((menu) => { |
| | | const path = menu.path?.replace(/^\//, ''); |
| | | return !(path === 'ai' || path?.startsWith('ai/')); |
| | | }) |
| | | .map((menu) => |
| | | menu.children?.length |
| | | ? { ...menu, children: filterAiMenus(menu.children) } |
| | | : menu, |
| | | ); |
| | | } |
| | | |
| | | export const useAuthStore = defineStore('auth', () => { |
| | | const accessStore = useAccessStore(); |
| | |
| | | userStore.setUserInfo(authPermissionInfo.user); |
| | | userStore.setUserRoles(authPermissionInfo.roles); |
| | | // accessStore |
| | | accessStore.setAccessMenus(authPermissionInfo.menus); |
| | | accessStore.setAccessMenus( |
| | | isAiEnable() ? authPermissionInfo.menus : filterAiMenus(authPermissionInfo.menus), |
| | | ); |
| | | accessStore.setAccessCodes(authPermissionInfo.permissions); |
| | | return authPermissionInfo; |
| | | } |
| | |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | |
| | | import { confirm, Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '#/packages/utils/src'; |
| | | |
| | |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import AgentImport from './modules/agent-import.vue'; |
| | | import Form from './modules/form.vue'; |
| | | import ImportForm from './modules/import-form.vue'; |
| | | |
| | |
| | | |
| | | const [ImportModal, importModalApi] = useVbenModal({ |
| | | connectedComponent: ImportForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [AgentImportModal, agentImportModalApi] = useVbenModal({ |
| | | connectedComponent: AgentImport, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | function handleImport() { |
| | | importModalApi.open(); |
| | | } |
| | | |
| | | /** æºè½ä½å¯¼å
¥ç©æ */ |
| | | function handleAgentImport() { |
| | | agentImportModalApi.open(); |
| | | } |
| | | |
| | | /** å·¥å
·æ æé®ï¼æºè½ä½å¯¼å
¥å AI æ»å¼å
³æ§å¶ */ |
| | | const toolbarActions = computed(() => [ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç©æ']), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mdm:item:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.import', ['ç©æ']), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mdm:item:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | ...(isAiEnable() |
| | | ? [ |
| | | { |
| | | label: 'æºè½ä½å¯¼å
¥', |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mdm:item:import'], |
| | | onClick: handleAgentImport, |
| | | }, |
| | | ] |
| | | : []), |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mdm:item:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.deleteBatch'), |
| | | type: 'primary' as const, |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mdm:item:delete'], |
| | | disabled: isEmpty(checkedIds), |
| | | onClick: handleDeleteBatch, |
| | | }, |
| | | ]); |
| | | |
| | | /** ç¼è¾ç©æ */ |
| | | function handleEdit(row: MdmItemApi.Item) { |
| | |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <AgentImportModal @success="handleRefresh" /> |
| | | <Grid table-title="ç©æå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç©æ']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mdm:item:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.import', ['ç©æ']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.UPLOAD, |
| | | auth: ['mdm:item:import'], |
| | | onClick: handleImport, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mdm:item:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.deleteBatch'), |
| | | type: 'primary', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mdm:item:delete'], |
| | | disabled: isEmpty(checkedIds), |
| | | onClick: handleDeleteBatch, |
| | | }, |
| | | ]" |
| | | /> |
| | | <TableAction :actions="toolbarActions" /> |
| | | </template> |
| | | <template #categoryName="{ row }"> |
| | | <Tag v-if="getCategoryColor(row.categoryId)" :color="getCategoryColor(row.categoryId)">{{ getCategoryName(row.categoryId) }}</Tag> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FileType } from 'ant-design-vue/es/upload/interface'; |
| | | |
| | | import type { MdmAgentImportApi } from '#/api/mdm/item/agent'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { |
| | | Alert, |
| | | Button, |
| | | Input, |
| | | message, |
| | | Steps, |
| | | Table, |
| | | Tag, |
| | | Upload, |
| | | } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | agentImportErrorMessage, |
| | | bindImportAttachment, |
| | | createImportSession, |
| | | getMaterialImportReceipt, |
| | | reviewMaterialImport, |
| | | startMaterialImport, |
| | | } from '#/api/mdm/item/agent'; |
| | | import { uploadFile } from '#/api/system/storage'; |
| | | |
| | | defineOptions({ name: 'MdmAgentImport' }); |
| | | |
| | | const emit = defineEmits<{ success: [] }>(); |
| | | |
| | | const MAX_BYTES = 8 * 1024 * 1024; |
| | | const FIELD_KEYS = [ |
| | | 'code', |
| | | 'name', |
| | | 'specification', |
| | | 'categoryCode', |
| | | 'unitMeasureCode', |
| | | ] as const; |
| | | type FieldKey = (typeof FIELD_KEYS)[number]; |
| | | |
| | | interface EditableRow { |
| | | candidate_id: string; |
| | | row_no?: number; |
| | | code: string; |
| | | name: string; |
| | | specification: string; |
| | | categoryCode: string; |
| | | unitMeasureCode: string; |
| | | original: Record<string, string>; |
| | | preflight?: MdmAgentImportApi.PreflightRow; |
| | | issues: string[]; |
| | | } |
| | | |
| | | interface RowMessage { |
| | | type: 'error' | 'warning'; |
| | | text: string; |
| | | } |
| | | |
| | | const step = ref(1); |
| | | const file = ref<File>(); |
| | | const fileName = ref(''); |
| | | const processing = ref(false); |
| | | const taskId = ref(''); |
| | | const state = ref<MdmAgentImportApi.ImportState>(); |
| | | const receipt = ref<MdmAgentImportApi.Receipt>(); |
| | | const rows = ref<EditableRow[]>([]); |
| | | const lastError = ref(''); |
| | | let sessionId: number | undefined; |
| | | let attachmentId: number | undefined; |
| | | |
| | | const preflight = computed(() => state.value?.preflight); |
| | | const issues = computed(() => state.value?.issues ?? []); |
| | | const canCommit = computed( |
| | | () => !!preflight.value?.can_commit && issues.value.length === 0, |
| | | ); |
| | | const hasChanges = computed(() => corrections.value.length > 0); |
| | | |
| | | const corrections = computed<MdmAgentImportApi.Correction[]>(() => { |
| | | const result: MdmAgentImportApi.Correction[] = []; |
| | | for (const row of rows.value) { |
| | | for (const field of FIELD_KEYS) { |
| | | const before = (row.original[field] ?? '').trim(); |
| | | const after = (row[field] ?? '').trim(); |
| | | if (before !== after) { |
| | | result.push({ |
| | | candidate_id: row.candidate_id, |
| | | field, |
| | | value: after, |
| | | reason: '人工修订', |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | return result; |
| | | }); |
| | | |
| | | const ACTION_LABEL: Record<MdmAgentImportApi.RowAction, string> = { |
| | | BLOCKED: '黿', |
| | | CREATE: 'æ°å»º', |
| | | SKIP: 'å·²åå¨', |
| | | }; |
| | | const ACTION_COLOR: Record<MdmAgentImportApi.RowAction, string> = { |
| | | BLOCKED: 'red', |
| | | CREATE: 'green', |
| | | SKIP: 'orange', |
| | | }; |
| | | const STATUS_LABEL: Record<MdmAgentImportApi.RowStatus, string> = { |
| | | FAILED: '失败', |
| | | SKIPPED: '已跳è¿', |
| | | SUCCESS: 'æå', |
| | | UNKNOWN: 'æªç¥', |
| | | }; |
| | | const STATUS_COLOR: Record<MdmAgentImportApi.RowStatus, string> = { |
| | | FAILED: 'red', |
| | | SKIPPED: 'orange', |
| | | SUCCESS: 'green', |
| | | UNKNOWN: 'default', |
| | | }; |
| | | |
| | | const reviewColumns = [ |
| | | { dataIndex: 'row_no', key: 'row_no', title: 'è¡å·', width: 64 }, |
| | | { dataIndex: 'code', key: 'code', title: 'ç©æç¼ç ', width: 150 }, |
| | | { dataIndex: 'name', key: 'name', title: 'ç©æåç§°', width: 180 }, |
| | | { dataIndex: 'specification', key: 'specification', title: 'è§æ ¼åå·', width: 150 }, |
| | | { dataIndex: 'categoryCode', key: 'categoryCode', title: 'åç±»ç¼ç ', width: 140 }, |
| | | { |
| | | dataIndex: 'unitMeasureCode', |
| | | key: 'unitMeasureCode', |
| | | title: '计éåä½', |
| | | width: 130, |
| | | }, |
| | | { key: 'action', title: '颿£å¨ä½', width: 100 }, |
| | | { key: 'message', title: 'æ ¡éªä¿¡æ¯', width: 260 }, |
| | | ]; |
| | | |
| | | const receiptColumns = [ |
| | | { dataIndex: 'row_no', key: 'row_no', title: 'è¡å·', width: 64 }, |
| | | { dataIndex: 'code', key: 'code', title: 'ç©æç¼ç ', width: 150 }, |
| | | { dataIndex: 'name', key: 'name', title: 'ç©æåç§°', width: 180 }, |
| | | { key: 'action', title: 'å¨ä½', width: 90 }, |
| | | { key: 'status', title: 'ç»æ', width: 90 }, |
| | | { dataIndex: 'target_item_id', key: 'target_item_id', title: 'ç©æ ID', width: 100 }, |
| | | { key: 'error', title: '失败åå ', width: 220 }, |
| | | ]; |
| | | |
| | | function isEditableColumn(key: unknown): key is FieldKey { |
| | | return typeof key === 'string' && (FIELD_KEYS as readonly string[]).includes(key); |
| | | } |
| | | |
| | | function rowMessages(row: EditableRow): RowMessage[] { |
| | | return [ |
| | | ...(row.preflight?.blockers ?? []).map((item) => ({ |
| | | type: 'error' as const, |
| | | text: item.message, |
| | | })), |
| | | ...(row.preflight?.warnings ?? []).map((item) => ({ |
| | | type: 'warning' as const, |
| | | text: item.message, |
| | | })), |
| | | ...row.issues.map((text) => ({ type: 'warning' as const, text })), |
| | | ]; |
| | | } |
| | | |
| | | function buildRows(data: MdmAgentImportApi.ImportState): EditableRow[] { |
| | | const plannedRows = data.preflight?.rows ?? []; |
| | | return (data.candidates ?? []) |
| | | .map((candidate) => { |
| | | const planned = plannedRows.find( |
| | | (item) => item.candidate_id === candidate.candidate_id, |
| | | ); |
| | | const values = candidate.values ?? {}; |
| | | return { |
| | | candidate_id: candidate.candidate_id, |
| | | row_no: planned?.row_no ?? candidate.evidence?.code?.row, |
| | | code: values.code ?? '', |
| | | name: values.name ?? '', |
| | | specification: values.specification ?? '', |
| | | categoryCode: values.categoryCode ?? '', |
| | | unitMeasureCode: values.unitMeasureCode ?? '', |
| | | original: { ...values }, |
| | | preflight: planned, |
| | | issues: candidate.issues ?? [], |
| | | }; |
| | | }) |
| | | .sort((a, b) => (a.row_no ?? 0) - (b.row_no ?? 0)); |
| | | } |
| | | |
| | | function applyState(payload: MdmAgentImportApi.PublicState) { |
| | | state.value = payload.state; |
| | | taskId.value = payload.state.task_id; |
| | | lastError.value = |
| | | payload.state.last_error || payload.interrupts?.[0]?.last_error || ''; |
| | | receipt.value = payload.state.receipt; |
| | | rows.value = buildRows(payload.state); |
| | | step.value = receipt.value ? 3 : 2; |
| | | syncFooter(); |
| | | } |
| | | |
| | | function syncFooter() { |
| | | void modalApi.setState({ |
| | | cancelText: step.value === 2 ? 'åæ¶ä»»å¡' : 'åæ¶', |
| | | confirmText: |
| | | step.value === 1 ? 'å¼å§è§£æ' : step.value === 2 ? '确认æäº¤' : '宿', |
| | | showCancelButton: step.value !== 3, |
| | | }); |
| | | } |
| | | |
| | | function reset() { |
| | | step.value = 1; |
| | | file.value = undefined; |
| | | fileName.value = ''; |
| | | processing.value = false; |
| | | taskId.value = ''; |
| | | state.value = undefined; |
| | | receipt.value = undefined; |
| | | rows.value = []; |
| | | lastError.value = ''; |
| | | sessionId = undefined; |
| | | attachmentId = undefined; |
| | | } |
| | | |
| | | function beforeUpload(fileInfo: FileType) { |
| | | const raw = fileInfo as unknown as File; |
| | | const name = raw.name.toLowerCase(); |
| | | if (!name.endsWith('.csv') && !name.endsWith('.xlsx')) { |
| | | message.error('ä»
æ¯æ CSV æ XLSX æä»¶'); |
| | | return false; |
| | | } |
| | | if (raw.size > MAX_BYTES) { |
| | | message.error('æä»¶ä¸è½è¶
è¿ 8 MiB'); |
| | | return false; |
| | | } |
| | | file.value = raw; |
| | | fileName.value = raw.name; |
| | | return false; |
| | | } |
| | | |
| | | async function startImport() { |
| | | if (!file.value) { |
| | | message.warning('请å
éæ© CSV/XLSX æä»¶'); |
| | | return; |
| | | } |
| | | processing.value = true; |
| | | modalApi.lock(); |
| | | try { |
| | | sessionId = await createImportSession(); |
| | | const blobs = await uploadFile([file.value]); |
| | | const blobId = blobs?.[0]?.id; |
| | | if (!blobId) { |
| | | throw new Error('æä»¶ä¸ä¼ å¤±è´¥ï¼æªåå¾ blobId'); |
| | | } |
| | | attachmentId = await bindImportAttachment(sessionId, blobId); |
| | | const payload = await startMaterialImport(attachmentId); |
| | | applyState(payload); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | processing.value = false; |
| | | modalApi.unlock(); |
| | | } |
| | | } |
| | | |
| | | async function revise() { |
| | | if (!state.value) return; |
| | | if (!hasChanges.value) { |
| | | message.info('æªæ£æµå°ä¿®æ¹'); |
| | | return; |
| | | } |
| | | processing.value = true; |
| | | modalApi.lock(); |
| | | try { |
| | | const payload = await reviewMaterialImport(taskId.value, { |
| | | action: 'revise', |
| | | corrections: corrections.value, |
| | | expected_revision: state.value.revision, |
| | | }); |
| | | applyState(payload); |
| | | message.success('å·²æä¿®æ¹éæ°é¢æ£ï¼è¯·æ ¸å¯¹ç»æ'); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | processing.value = false; |
| | | modalApi.unlock(); |
| | | } |
| | | } |
| | | |
| | | async function confirmSubmit() { |
| | | if (!state.value) return; |
| | | if (hasChanges.value) { |
| | | message.warning('å卿ªæäº¤çä¿®æ¹ï¼è¯·å
ç¹ãéæ°æ£æ¥ãå¹¶ææ°ç颿£ç»æç¡®è®¤'); |
| | | return; |
| | | } |
| | | if (!canCommit.value) { |
| | | message.warning(lastError.value || '颿£ä»æé»æé¡¹ï¼è¯·ä¿®è®¢åéæ°æ£æ¥'); |
| | | return; |
| | | } |
| | | processing.value = true; |
| | | modalApi.lock(); |
| | | try { |
| | | const payload = await reviewMaterialImport(taskId.value, { |
| | | action: 'confirm', |
| | | expected_preflight_hash: state.value.preflight_hash, |
| | | expected_revision: state.value.revision, |
| | | }); |
| | | applyState(payload); |
| | | if (receipt.value) { |
| | | message.success('æäº¤å®æï¼è¯·æ¥çåæ§'); |
| | | } else if (lastError.value) { |
| | | message.error(lastError.value); |
| | | } |
| | | } catch (error) { |
| | | const text = agentImportErrorMessage(error); |
| | | message.error(text); |
| | | // æäº¤è¶
æ¶/ç½å
³ä¸ææ¶å½ä»¤å¯è½å·²æ§è¡ï¼æç¤ºç¨æ·æ¥åæ§ï¼é¿å
éå¤æäº¤ |
| | | lastError.value = text; |
| | | } finally { |
| | | processing.value = false; |
| | | modalApi.unlock(); |
| | | } |
| | | } |
| | | |
| | | async function fetchReceipt() { |
| | | if (!taskId.value) return; |
| | | processing.value = true; |
| | | modalApi.lock(); |
| | | try { |
| | | receipt.value = await getMaterialImportReceipt(taskId.value); |
| | | step.value = 3; |
| | | syncFooter(); |
| | | } catch (error) { |
| | | message.error(agentImportErrorMessage(error)); |
| | | } finally { |
| | | processing.value = false; |
| | | modalApi.unlock(); |
| | | } |
| | | } |
| | | |
| | | async function handleCancel() { |
| | | if (step.value === 2 && taskId.value) { |
| | | try { |
| | | await reviewMaterialImport(taskId.value, { |
| | | action: 'cancel', |
| | | expected_revision: state.value?.revision ?? 0, |
| | | }); |
| | | } catch { |
| | | // åæ¶å¤±è´¥ä¸å½±åå
³éçªå£ |
| | | } |
| | | } |
| | | modalApi.close(); |
| | | } |
| | | |
| | | function finish() { |
| | | if ((receipt.value?.summary.success ?? 0) > 0) { |
| | | emit('success'); |
| | | } |
| | | modalApi.close(); |
| | | } |
| | | |
| | | async function handlePrimary() { |
| | | if (processing.value) return; |
| | | if (step.value === 1) { |
| | | await startImport(); |
| | | return; |
| | | } |
| | | if (step.value === 2) { |
| | | await confirmSubmit(); |
| | | return; |
| | | } |
| | | finish(); |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | onCancel() { |
| | | void handleCancel(); |
| | | }, |
| | | onClosed() { |
| | | reset(); |
| | | }, |
| | | onConfirm() { |
| | | void handlePrimary(); |
| | | }, |
| | | onOpenChange(isOpen) { |
| | | if (isOpen) { |
| | | reset(); |
| | | syncFooter(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="æºè½ä½å¯¼å
¥ç©æ" class="w-[1080px]"> |
| | | <Steps |
| | | :current="step - 1" |
| | | class="mx-4 mb-4" |
| | | size="small" |
| | | :items="[ |
| | | { title: 'ä¸ä¼ æä»¶' }, |
| | | { title: 'å®¡æ ¸ç¡®è®¤' }, |
| | | { title: 'æäº¤åæ§' }, |
| | | ]" |
| | | /> |
| | | |
| | | <div v-if="step === 1" class="mx-4"> |
| | | <Upload |
| | | accept=".csv,.xlsx" |
| | | :max-count="1" |
| | | :show-upload-list="false" |
| | | :before-upload="beforeUpload" |
| | | > |
| | | <Button type="primary">éæ© CSV / XLSX æä»¶</Button> |
| | | </Upload> |
| | | <div v-if="fileName" class="mt-2 text-sm text-gray-600"> |
| | | 已鿩ï¼{{ fileName }} |
| | | </div> |
| | | <div class="mt-3 text-xs text-gray-500 leading-relaxed"> |
| | | <div>æä»¶ä¸é 8 MiBï¼ä»
æ¯æ CSV / XLSXã</div> |
| | | <div> |
| | | æµç¨ï¼è§£ææ¸
å â Java æ ¡éªåç±»/åä½/ç¼ç â äººå·¥æ ¸å¯¹ â 确认æäº¤ã |
| | | </div> |
| | | <div> |
| | | æ¬é¶æ®µåªæ°å»ºç©æï¼ç¼ç å·²åå¨ä¼è·³è¿ï¼ä¸æ´æ°åç©æï¼ä¹ä¸åæ¥ MESã |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="step === 2" class="mx-4"> |
| | | <div class="mb-3 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm"> |
| | | <span>æ»è®¡ï¼{{ preflight?.summary.total ?? rows.length }}</span> |
| | | <span class="text-green-600"> |
| | | æ°å»ºï¼{{ preflight?.summary.create ?? 0 }} |
| | | </span> |
| | | <span class="text-orange-500"> |
| | | è·³è¿ï¼{{ preflight?.summary.skip ?? 0 }} |
| | | </span> |
| | | <span class="text-red-500"> |
| | | 黿ï¼{{ preflight?.summary.blocked ?? 0 }} |
| | | </span> |
| | | <span class="text-gray-500">æ¹æ¡çæ¬ï¼{{ state?.revision ?? 0 }}</span> |
| | | </div> |
| | | |
| | | <Alert |
| | | v-if="lastError" |
| | | type="error" |
| | | :message="lastError" |
| | | show-icon |
| | | class="mb-3" |
| | | /> |
| | | |
| | | <Alert |
| | | v-if="issues.length > 0" |
| | | type="warning" |
| | | show-icon |
| | | class="mb-3" |
| | | message="åå¨éè¦å¤ççé®é¢" |
| | | > |
| | | <template #description> |
| | | <div v-for="(issue, index) in issues" :key="index"> |
| | | {{ issue.message }} |
| | | </div> |
| | | </template> |
| | | </Alert> |
| | | |
| | | <Alert |
| | | v-else-if="preflight && !preflight.can_commit" |
| | | type="warning" |
| | | show-icon |
| | | class="mb-3" |
| | | message="颿£ä»æé»æé¡¹ï¼ä¿®æ£å请ç¹ãéæ°æ£æ¥ãï¼éè¿åæè½ç¡®è®¤æäº¤" |
| | | /> |
| | | |
| | | <Table |
| | | bordered |
| | | size="small" |
| | | :columns="reviewColumns" |
| | | :data-source="rows" |
| | | :pagination="false" |
| | | :row-key="(record: EditableRow) => record.candidate_id" |
| | | :scroll="{ y: 360 }" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="isEditableColumn(column.key)"> |
| | | <Input |
| | | v-model:value="record[column.key]" |
| | | size="small" |
| | | :status=" |
| | | record.preflight?.blockers?.some( |
| | | (item: MdmAgentImportApi.Issue) => item.field === column.key, |
| | | ) |
| | | ? 'error' |
| | | : undefined |
| | | " |
| | | /> |
| | | <div |
| | | v-if=" |
| | | column.key === 'categoryCode' && |
| | | record.preflight?.resolved?.category_name |
| | | " |
| | | class="mt-1 text-xs text-gray-400" |
| | | > |
| | | è§£æä¸ºï¼{{ record.preflight.resolved.category_name }} |
| | | </div> |
| | | <div |
| | | v-if=" |
| | | column.key === 'unitMeasureCode' && |
| | | record.preflight?.resolved?.unit_measure_name |
| | | " |
| | | class="mt-1 text-xs text-gray-400" |
| | | > |
| | | è§£æä¸ºï¼{{ record.preflight.resolved.unit_measure_name }} |
| | | </div> |
| | | </template> |
| | | <template v-else-if="column.key === 'action'"> |
| | | <Tag |
| | | v-if="record.preflight" |
| | | :color=" |
| | | ACTION_COLOR[record.preflight.action as MdmAgentImportApi.RowAction] |
| | | " |
| | | > |
| | | {{ ACTION_LABEL[record.preflight.action as MdmAgentImportApi.RowAction] }} |
| | | </Tag> |
| | | </template> |
| | | <template v-else-if="column.key === 'message'"> |
| | | <div |
| | | v-for="(item, index) in rowMessages(record as EditableRow)" |
| | | :key="index" |
| | | class="text-xs leading-relaxed" |
| | | :class="item.type === 'error' ? 'text-red-500' : 'text-orange-500'" |
| | | > |
| | | {{ item.text }} |
| | | </div> |
| | | </template> |
| | | </template> |
| | | </Table> |
| | | |
| | | <div class="mt-3 flex flex-wrap items-center gap-2"> |
| | | <Button :disabled="processing || !hasChanges" @click="revise"> |
| | | éæ°æ£æ¥ |
| | | </Button> |
| | | <Button :disabled="processing || !taskId" @click="fetchReceipt"> |
| | | æ¥è¯¢åæ§ |
| | | </Button> |
| | | <span class="text-xs text-gray-400"> |
| | | {{ hasChanges ? '已修æ¹ï¼ééæ°æ£æ¥åæè½ç¡®è®¤' : '确认åæäº¤ï¼å·²åå¨ç¼ç ä¼è·³è¿' }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else class="mx-4"> |
| | | <div class="mb-3 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm"> |
| | | <Tag :color="receipt?.status === 'SUCCEEDED' ? 'green' : 'orange'"> |
| | | {{ receipt?.status ?? 'UNKNOWN' }} |
| | | </Tag> |
| | | <span>æ»è®¡ï¼{{ receipt?.summary.total ?? 0 }}</span> |
| | | <span class="text-green-600"> |
| | | æåï¼{{ receipt?.summary.success ?? 0 }} |
| | | </span> |
| | | <span class="text-orange-500"> |
| | | è·³è¿ï¼{{ receipt?.summary.skipped ?? 0 }} |
| | | </span> |
| | | <span class="text-red-500"> |
| | | 失败ï¼{{ receipt?.summary.failed ?? 0 }} |
| | | </span> |
| | | <span class="text-gray-500"> |
| | | æªç¥ï¼{{ receipt?.summary.unknown ?? 0 }} |
| | | </span> |
| | | </div> |
| | | |
| | | <Alert |
| | | v-if="receipt?.error_message" |
| | | type="error" |
| | | :message="receipt.error_message" |
| | | show-icon |
| | | class="mb-3" |
| | | /> |
| | | |
| | | <Table |
| | | bordered |
| | | size="small" |
| | | :columns="receiptColumns" |
| | | :data-source="receipt?.rows ?? []" |
| | | :pagination="false" |
| | | :row-key="(record: MdmAgentImportApi.ReceiptRow) => record.candidate_id" |
| | | :scroll="{ y: 360 }" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.key === 'action'"> |
| | | <Tag :color="record.action === 'CREATE' ? 'green' : 'orange'"> |
| | | {{ record.action === 'CREATE' ? 'æ°å»º' : 'è·³è¿' }} |
| | | </Tag> |
| | | </template> |
| | | <template v-else-if="column.key === 'status'"> |
| | | <Tag :color="STATUS_COLOR[record.status as MdmAgentImportApi.RowStatus]"> |
| | | {{ STATUS_LABEL[record.status as MdmAgentImportApi.RowStatus] }} |
| | | </Tag> |
| | | </template> |
| | | <template v-else-if="column.key === 'error'"> |
| | | <span class="text-xs text-red-500"> |
| | | {{ record.error_message ?? '' }} |
| | | </span> |
| | | </template> |
| | | </template> |
| | | </Table> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| | |
| | | |
| | | import type { CrmSaleQuotationAiApi } from '#/api/crm/saleQuotation/ai'; |
| | | |
| | | import { nextTick, ref } from 'vue'; |
| | | import { computed, nextTick, ref } from 'vue'; |
| | | |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | |
| | | import { QUOTATION_STATUS, useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | |
| | | }); |
| | | } |
| | | |
| | | /** å·¥å
·æ æé®ï¼è¯å«å½å
¥å AI æ»å¼å
³æ§å¶ */ |
| | | const toolbarActions = computed(() => [ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥ä»·å']), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['crm:sale-quotation:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ...(isAiEnable() |
| | | ? [ |
| | | { |
| | | label: 'è¯å«å½å
¥', |
| | | type: 'default' as const, |
| | | auth: ['crm:sale-quotation:create'], |
| | | onClick: handleOcrCreate, |
| | | }, |
| | | ] |
| | | : []), |
| | | ]); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | |
| | | <OcrUpload ref="ocrUploadRef" @success="handleOcrSuccess" /> |
| | | <Grid> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥ä»·å']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['crm:sale-quotation:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'è¯å«å½å
¥', |
| | | type: 'default', |
| | | auth: ['crm:sale-quotation:create'], |
| | | onClick: handleOcrCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | <TableAction :actions="toolbarActions" /> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | |
| | | import { Col, Row } from 'ant-design-vue'; |
| | | |
| | |
| | | |
| | | <!-- AI æºè½åæ --> |
| | | <AiInsights |
| | | v-if="canViewHome && canNavigate('MesProWorkOrder')" |
| | | v-if="canViewHome && canNavigate('MesProWorkOrder') && isAiEnable()" |
| | | :active-order-count="summary.workOrderActiveCount" |
| | | @navigate="handleNavigate" |
| | | /> |
| | |
| | | import type { ErpPurchaseInvoiceAiApi } from '#/api/erp/purchase/invoice/ai'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message, Tag } from 'ant-design-vue'; |
| | | import { nextTick, ref } from 'vue'; |
| | | import { computed, nextTick, ref } from 'vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | } |
| | | } |
| | | |
| | | /** å·¥å
·æ æé®ï¼è¯å«å½å
¥å AI æ»å¼å
³æ§å¶ */ |
| | | const toolbarActions = computed(() => [ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥ç¥¨']), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-invoice:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ...(isAiEnable() |
| | | ? [ |
| | | { |
| | | label: 'è¯å«å½å
¥', |
| | | type: 'primary' as const, |
| | | auth: ['erp:purchase-invoice:create'], |
| | | onClick: handleOcrCreate, |
| | | }, |
| | | ] |
| | | : []), |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary' as const, |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['erp:purchase-invoice:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | |
| | | <OcrUpload ref="ocrUploadRef" @success="handleOcrSuccess" /> |
| | | <Grid table-title="æ¥ç¥¨å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¥ç¥¨']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-invoice:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'è¯å«å½å
¥', |
| | | type: 'primary', |
| | | auth: ['erp:purchase-invoice:create'], |
| | | onClick: handleOcrCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['erp:purchase-invoice:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | <TableAction :actions="toolbarActions" /> |
| | | </template> |
| | | <template #no="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | |
| | | import { |
| | | MesProWorkOrderStatusEnum, |
| | | } from "@vben/constants"; |
| | | import { isAiEnable } from "@vben/hooks"; |
| | | |
| | | import { Button, message, Tabs } from "ant-design-vue"; |
| | | |
| | |
| | | ); |
| | | const canUseAi = computed( |
| | | () => |
| | | isAiEnable() && |
| | | formData.value?.id && |
| | | (formData.value?.status === MesProWorkOrderStatusEnum.PREPARE || |
| | | formData.value?.status === MesProWorkOrderStatusEnum.CONFIRMED) |
| | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | |
| | | import { Empty, Input, Spin } from 'ant-design-vue'; |
| | | |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <a-button type="default" @click="modalApi.open()"> |
| | | <a-button v-if="isAiEnable()" type="default" @click="modalApi.open()"> |
| | | è´¨æ£å©æ |
| | | </a-button> |
| | | |
| | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { isAiEnable } from '@vben/hooks'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Alert, Button, message, Tag } from 'ant-design-vue'; |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Button type="default" :loading="loading" @click="handleSuggest"> |
| | | <Button |
| | | v-if="isAiEnable()" |
| | | type="default" |
| | | :loading="loading" |
| | | @click="handleSuggest" |
| | | > |
| | | <template #icon> |
| | | <IconifyIcon icon="ant-design:robot-outlined" /> |
| | | </template> |
| | |
| | | |
| | | import { useAccess } from '@vben/access'; |
| | | import { Page } from '@vben/common-ui'; |
| | | import { useTabs } from '@vben/hooks'; |
| | | import { isAiEnable, useTabs } from '@vben/hooks'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { |
| | |
| | | const { hasAccessByCodes } = useAccess(); |
| | | |
| | | /** AI 导å
¥æ¯ç¬ç«æéç ï¼è½æ¹æ¨¡æ¿ä¸çäºè½ç¨å¤§æ¨¡åè¯å«æä»¶ï¼æ¯æ¬¡è°ç¨é½è¦è±é±ï¼ */ |
| | | const canAiImport = computed(() => |
| | | hasAccessByCodes(['qc-report:template:ai-import']), |
| | | const canAiImport = computed( |
| | | () => isAiEnable() && hasAccessByCodes(['qc-report:template:ai-import']), |
| | | ); |
| | | |
| | | const loading = ref(true); |
| | |
| | | target: "http://localhost:48080", |
| | | ws: true, |
| | | }, |
| | | "/agent-api": { |
| | | changeOrigin: true, |
| | | target: "http://127.0.0.1:8091", |
| | | rewrite: (path) => path.replace(/^\/agent-api/, ""), |
| | | ws: true, |
| | | }, |
| | | }, |
| | | }, |
| | | }, |