Merge remote-tracking branch 'origin/dev_南通_苏珩' into dev_南通_苏珩
| | |
| | | params: { itemId }, |
| | | }); |
| | | } |
| | | |
| | | /** ä»ç©æç§»é¤ææ¯æä»¶ï¼å·²çæå·¥åä»ä¿çæä»¶å¿«ç
§ã */ |
| | | export function deleteTechnicalFile(itemId: number, id: number) { |
| | | return requestClient.delete('/mdm/item/technical-file/delete', { params: { itemId, id } }); |
| | | } |
| | |
| | | ); |
| | | } |
| | | |
| | | /** æå·¥ä½ç«ç»è®¡å½åç¨æ·å¯è§çæªå®æä»»å¡ã */ |
| | | export function getUnfinishedWorkstationCounts() { |
| | | return requestClient.get<Array<{ workstationId: number; pendingCount: number }>>( |
| | | '/mes/pro/task/unfinished-workstation-counts', |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®å·¥ä½ç«åæ°é计ç®ç产æ¶é¿ */ |
| | | export function calculateTaskDuration(workstationId: number, quantity: number) { |
| | | return requestClient.get<{ duration: number }>( |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesQcSourceLinkApi { |
| | | /** å
³èç±»åï¼feedback = ç产æ¥å·¥ï¼sales = éå®åºåº */ |
| | | export type LinkType = 'feedback' | 'sales'; |
| | | |
| | | /** å¯å
³èçå¾
æ£æ¥æºæç»åé */ |
| | | export interface Candidate { |
| | | lineId: number; // å¾
æ£æç» IDï¼æäº¤å
³èæ¶åä¼ |
| | | documentId: number; // æ¥æºåæ® ID |
| | | documentCode: string; // æ¥æºåæ®ç¼å· |
| | | reelNo: string; // çå· |
| | | quantity: number; // å¾
æ£æ°é |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢å¯å
³èçå¾
æ£æ¥æºåé */ |
| | | export function getSourceLinkCandidates( |
| | | type: MesQcSourceLinkApi.LinkType, |
| | | qcId: number, |
| | | ) { |
| | | return requestClient.get<MesQcSourceLinkApi.Candidate[]>( |
| | | `/mes/qc/source-link/${type}-candidates`, |
| | | { params: { qcId } }, |
| | | ); |
| | | } |
| | | |
| | | /** å°è´¨æ£åå
³è尿宿¥æºæç» */ |
| | | export function linkSource( |
| | | type: MesQcSourceLinkApi.LinkType, |
| | | qcId: number, |
| | | lineId: number, |
| | | ) { |
| | | return requestClient.post<boolean>( |
| | | `/mes/qc/source-link/${type}`, |
| | | null, |
| | | { params: { lineId, qcId } }, |
| | | ); |
| | | } |
| | |
| | | >('/mes/wm/product-issue-line/page', { params }); |
| | | } |
| | | |
| | | /** æ¥è¯¢é¢æåå
¨é¨æç»ï¼ç¨äºæå°ã */ |
| | | export function getProductIssueLinesByIssue(issueId: number) { |
| | | return requestClient.get<MesWmProductIssueLineApi.ProductIssueLine[]>( |
| | | '/mes/wm/product-issue-line/list-by-issue', |
| | | { params: { issueId } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢é¢æåºåºåè¡è¯¦æ
*/ |
| | | export function getProductIssueLine(id: number) { |
| | | return requestClient.get<MesWmProductIssueLineApi.ProductIssueLine>( |
| | |
| | | batchId?: number; // æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | reelNo?: string; // é宿ç»çå· |
| | | oqcCheckFlag?: boolean; // æ¯å¦æ£éª |
| | | oqcCheckFlag?: boolean; // æ¯å¦éè¦åºè´§æ£éª |
| | | oqcId?: number; // å
³èçåºè´§æ£éªå ID |
| | | qualityStatus?: number; // è´¨éç¶æï¼åå
¸ mes_wm_quality_statusï¼ |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | } |
| | |
| | | isInnerOperate.value = false; |
| | | return; |
| | | } |
| | | let value: string[] = []; |
| | | let value: Array<number | string> = []; |
| | | if (v) { |
| | | if (Array.isArray(v)) { |
| | | value = v; |
| | |
| | | helpText?: string; |
| | | listType?: UploadListType; |
| | | maxNumber?: number; // æå¤§æ°éçæä»¶ï¼Infinityä¸éå¶ |
| | | modelValue?: string | string[] | number[]; // v-model æ¯æ |
| | | modelValue?: string | number | string[] | number[]; // v-model æ¯æ |
| | | maxSize?: number; // æä»¶æå¤§å¤å°MB |
| | | multiple?: boolean; // æ¯å¦æ¯æå¤é |
| | | resultField?: string; // support xxx.xxx.xx |
| | | returnText?: boolean; // æ¯å¦è¿åæä»¶ææ¬å
容 |
| | | showDescription?: boolean; // æ¯å¦æ¾ç¤ºä¸é¢çæè¿° |
| | | showDownloadIcon?: boolean; // æ¯å¦æ¾ç¤ºä¸è½½æé® |
| | | value?: string | string[] | number[]; |
| | | value?: string | number | string[] | number[]; |
| | | valueKey?: string; // è¿åå¼çåæ®µåï¼é»è®¤ 'url'ï¼è®¾ä¸º 'id' æ¶è¿å blobId |
| | | valuePrefix?: string; // valueKey è¿åå¼çåç¼ |
| | | } |
| | |
| | | |
| | | import { useVbenModal } from "#/packages/effects/common-ui/src"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | import { message, Popconfirm } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createItem, |
| | | createTechnicalFile, |
| | | deleteTechnicalFile, |
| | | getItem, |
| | | getTechnicalFileList, |
| | | updateItem, |
| | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MdmItemApi.Item>(); |
| | | const technicalFiles = ref<MdmItemApi.TechnicalFile[]>([]); |
| | | const technicalUploadValue = ref<number[]>([]); |
| | | const technicalUploadValue = ref<number | number[] | string>([]); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | |
| | | }); |
| | | |
| | | async function handleTechnicalFileChange(value: unknown) { |
| | | if (!formData.value?.id || !Array.isArray(value)) { |
| | | if (!formData.value?.id) { |
| | | return; |
| | | } |
| | | const blobId = value.find((item): item is number => typeof item === 'number'); |
| | | if (!blobId) { |
| | | // åæä»¶ä¸ä¼ ç»ä»¶è¿åå个 IDï¼å
¼å®¹æ°ç»åæ°åå符串ã |
| | | const rawId = Array.isArray(value) ? value[0] : value; |
| | | const blobId = typeof rawId === 'number' ? rawId : |
| | | typeof rawId === 'string' && /^\d+$/.test(rawId) ? Number(rawId) : 0; |
| | | if (!Number.isSafeInteger(blobId) || blobId <= 0) { |
| | | return; |
| | | } |
| | | try { |
| | | await createTechnicalFile(formData.value.id, blobId); |
| | | technicalFiles.value = await getTechnicalFileList(formData.value.id); |
| | | message.success('ææ¯æä»¶å·²ä¿åï¼åå²çæ¬å·²ä¿ç'); |
| | | message.success('ææ¯æä»¶å·²æ·»å '); |
| | | } finally { |
| | | technicalUploadValue.value = []; |
| | | } |
| | | } |
| | | |
| | | async function handleDeleteTechnicalFile(id: number) { |
| | | const itemId = formData.value?.id; |
| | | if (!itemId) return; |
| | | await deleteTechnicalFile(itemId, id); |
| | | technicalFiles.value = await getTechnicalFileList(itemId); |
| | | message.success('ææ¯æä»¶å·²ç§»é¤'); |
| | | } |
| | | </script> |
| | | |
| | |
| | | <Modal class="w-[1000px]" :title="formData?.id ? $t('ui.actionTitle.edit', ['ç©æ']) : $t('ui.actionTitle.create', ['ç©æ'])"> |
| | | <Form class="mx-4" /> |
| | | <div v-if="formData?.id" class="mx-4 mb-4 rounded border p-4"> |
| | | <div class="mb-3 text-base font-medium">ææ¯æä»¶åå²</div> |
| | | <div class="mb-3 text-base font-medium">ææ¯æä»¶</div> |
| | | <FileUpload |
| | | v-model:value="technicalUploadValue" |
| | | value-key="id" |
| | | :max-number="1" |
| | | :multiple="false" |
| | | :accept="['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.dwg', '.png', '.jpg', '.jpeg']" |
| | | help-text="ä¸ä¼ æ°æä»¶ä¼çææ°ççæ¬ï¼å岿件ä»å¯æ¥ç" |
| | | :accept="['pdf', 'doc', 'docx', 'xls', 'xlsx', 'dwg', 'png', 'jpg', 'jpeg']" |
| | | help-text="æ¯æ¬¡ä¸ä¼ ä¼è¿½å ä¸ä»½ææ¯æä»¶ï¼åæä»¶ç»§ç»ä¿ç" |
| | | @change="handleTechnicalFileChange" |
| | | /> |
| | | <a-table |
| | |
| | | :pagination="false" |
| | | row-key="id" |
| | | > |
| | | <a-table-column key="versionNo" title="çæ¬" data-index="versionNo" /> |
| | | <a-table-column key="versionNo" title="ä¸ä¼ åºå·" data-index="versionNo" /> |
| | | <a-table-column key="fileName" title="æä»¶å" data-index="fileName" /> |
| | | <a-table-column key="createTime" title="ä¸ä¼ æ¶é´" data-index="createTime" /> |
| | | <a-table-column key="action" title="æä½"> |
| | | <template #default="{ record }"> |
| | | <a :href="record.downloadURL || record.previewURL" target="_blank">æ¥çæä»¶</a> |
| | | <Popconfirm title="ç§»é¤è¯¥ææ¯æä»¶ï¼å·²æå·¥åä»ä¿çåæä»¶ï¼æ°å·¥åå°ä¸å带å
¥ã" ok-text="ç§»é¤" cancel-text="åæ¶" @confirm="handleDeleteTechnicalFile(record.id)"> |
| | | <a class="ml-3 text-red-500">å é¤</a> |
| | | </Popconfirm> |
| | | </template> |
| | | </a-table-column> |
| | | </a-table> |
| | |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { useAccess } from '@vben/access'; |
| | | import { useTabs } from '@vben/hooks'; |
| | | |
| | | import { Card, Tabs } from 'ant-design-vue'; |
| | |
| | | import ContractDetailsInfo from './modules/info.vue'; |
| | | |
| | | const props = defineProps<{ id?: number }>(); |
| | | const { hasAccessByRoles } = useAccess(); |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | |
| | | label: 'å¯¼åº Word', |
| | | type: 'primary', |
| | | icon: 'lucide:download', |
| | | auth: ['crm:contract:query'], |
| | | auth: hasAccessByRoles(['super_admin']) ? [] : ['crm:contract:query'], |
| | | onClick: () => downloadContractWord(contractId, contract.no), |
| | | }, |
| | | { |
| | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { useAccess } from '@vben/access'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message, Modal, Select, Tabs, Tag, Tooltip } from 'ant-design-vue'; |
| | |
| | | import ImportForm from './modules/import-form.vue'; |
| | | |
| | | const { push } = useRouter(); |
| | | const { hasAccessByRoles } = useAccess(); |
| | | const sceneType = ref('1'); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | |
| | | label: 'å¯¼åº Word', |
| | | type: 'link', |
| | | icon: 'lucide:download', |
| | | auth: ['crm:contract:query'], |
| | | auth: hasAccessByRoles(['super_admin']) ? [] : ['crm:contract:query'], |
| | | onClick: () => downloadContractWord(row.id, row.no), |
| | | }, |
| | | { |
| | |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'ç¨é¢å计', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 80, |
| | | fixed: 'right', |
| | |
| | | (sum, item) => sum + (item.taxPrice || 0), |
| | | 0, |
| | | ), |
| | | totalPrice: tableData.value.reduce( |
| | | (sum, item) => sum + (item.totalPrice || 0), |
| | | 0, |
| | | ), |
| | | }; |
| | | }); |
| | | |
| | |
| | | éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | </span> |
| | | <span>ç¨é¢ï¼{{ erpPriceInputFormatter(summaries.taxPrice) }}</span> |
| | | <span> |
| | | ç¨é¢å计ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createMps, getMps, updateMps } from '#/api/mes/pro/mps'; |
| | | import { $t } from '#/locales'; |
| | | import TechnicalFiles from '../../workorder/modules/technical-files.vue'; |
| | | |
| | | import PredictDelivery from '../../workorder/modules/ai/predict-delivery.vue'; |
| | | import PredictDuration from '../../workorder/modules/ai/predict-duration.vue'; |
| | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesProMpsApi.Mps>(); |
| | | const formType = ref<FormType>('detail'); |
| | | const selectedProductId = ref<number>(); |
| | | |
| | | const isEditable = computed(() => ['create', 'edit'].includes(formType.value)); |
| | | |
| | |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-3', |
| | | handleValuesChange: (values, changedFields) => { |
| | | if (changedFields.includes('productId')) selectedProductId.value = values.productId; |
| | | }, |
| | | }); |
| | | |
| | | /** éæ°æè½½ schema å¹¶æè¡¨åææ§å¶åºé¨ç¡®è®¤æé® */ |
| | |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | selectedProductId.value = undefined; |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getMps(data.id); |
| | | selectedProductId.value = formData.value.productId; |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | <div class="mx-4 mb-4 rounded border p-4"> |
| | | <div class="mb-3 font-medium">ææ¯æä»¶</div> |
| | | <TechnicalFiles :work-order-id="formData?.workOrderId" :item-id="selectedProductId" /> |
| | | </div> |
| | | <!-- 详æ
模å¼é¢å¤å±ç¤ºåªè¯»ä¿¡æ¯ --> |
| | | <div v-if="formType === 'detail' && formData" class="mx-4"> |
| | | <Descriptions bordered :column="2"> |
| | |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import Form from "./modules/form.vue"; |
| | | import ScrapModal from "./modules/scrap-modal.vue"; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | |
| | | const archiveVisible = ref(false); |
| | | const archiveProductCode = ref(''); |
| | | const scrapModalRef = ref<InstanceType<typeof ScrapModal>>(); |
| | | // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // æ¡ç 详æ
å¼¹çª |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | |
| | | // onClick: handleBarcode.bind(null, row), |
| | | // }, |
| | | { |
| | | label: 'åºæ', |
| | | type: 'link', |
| | | auth: ['mes:wm-scrap-receipt:create'], |
| | | ifShow: row.status === MesProWorkOrderStatusEnum.FINISHED, |
| | | onClick: () => scrapModalRef?.open(row.id, row.code), |
| | | }, |
| | | { |
| | | label: '彿¡£', |
| | | type: 'link', |
| | | onClick: handleArchive.bind(null, row), |
| | |
| | | </Grid> |
| | | <!-- <BarcodeDetail ref="barcodeDetailRef" /> --> |
| | | <ArchiveDetail :visible="archiveVisible" :product-code="archiveProductCode" @update:visible="archiveVisible = $event" /> |
| | | <ScrapModal ref="scrapModalRef" @success="handleRefresh" /> |
| | | </Page> |
| | | </template> |
| | | |
| | |
| | | import PredictRisk from "./ai/predict-risk.vue"; |
| | | import ItemList from "./item-list.vue"; |
| | | import ProcessList from "./process-list.vue"; |
| | | import TechnicalFiles from "./technical-files.vue"; |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | const formType = ref<FormType>("create"); |
| | |
| | | @process-list-change="handleProcessListChange" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="technical-files" tab="ææ¯æä»¶"> |
| | | <TechnicalFiles v-if="formData?.id" :work-order-id="formData.id" :key="formData.id" /> |
| | | <div v-else class="text-gray-400 text-center py-8">ä¿å工忶å°çåæé产åçææ¯æä»¶</div> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="item" |
| | | tab="ç©æéæ±"> |
| | | <p class="mb-3 text-xs text-gray-400 leading-relaxed"> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, ref } from 'vue'; |
| | | import { Button, Form, FormItem, Input, InputNumber, Modal, Select, message } from 'ant-design-vue'; |
| | | import { requestClient } from '#/api/request'; |
| | | import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse'; |
| | | import { WmWarehouseAreaSelect, WmWarehouseLocationSelect } from '#/views/wls/warehouse/components'; |
| | | |
| | | const emit = defineEmits<{ success: [] }>(); |
| | | const visible = ref(false); |
| | | const busy = ref(false); |
| | | const orderId = ref<number>(); |
| | | const orderCode = ref(''); |
| | | const requestId = ref(''); |
| | | const outputs = ref<Array<{ itemId: number; itemCode?: string; itemName?: string; quantity: number; batchId?: number; batchCode?: string }>>([]); |
| | | const warehouses = ref<Array<{ id?: number; name?: string; warehouseType?: string; frozen?: boolean }>>([]); |
| | | const itemId = ref<number>(); |
| | | const selectedOutput = ref<number>(); |
| | | const batchId = ref<number>(); |
| | | const quantity = ref<number>(); |
| | | const warehouseId = ref<number>(); |
| | | const locationId = ref<number>(); |
| | | const areaId = ref<number>(); |
| | | const batchCode = ref(''); |
| | | const options = computed(() => outputs.value.map((item, index) => ({ value: index, label: `${item.itemName || item.itemCode || item.itemId}${item.batchCode ? ` · æ¹æ¬¡ ${item.batchCode}` : ''}ï¼äº§åº ${item.quantity}ï¼` }))); |
| | | function selectOutput(index: number) { |
| | | const output = outputs.value[index]; |
| | | itemId.value = output?.itemId; batchId.value = output?.batchId; batchCode.value = output?.batchCode || ''; |
| | | } |
| | | |
| | | async function open(id: number, code?: string) { |
| | | orderId.value = id; orderCode.value = code || ''; itemId.value = undefined; selectedOutput.value = undefined; batchId.value = undefined; quantity.value = undefined; |
| | | warehouseId.value = undefined; locationId.value = undefined; areaId.value = undefined; batchCode.value = ''; |
| | | requestId.value = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`; |
| | | outputs.value = []; visible.value = true; busy.value = true; |
| | | try { |
| | | const [items, list] = await Promise.all([ |
| | | requestClient.get<typeof outputs.value>('/mes/wm/scrap-receipt/order-outputs', { params: { workOrderId: id } }), |
| | | getWarehouseSimpleList(), |
| | | ]); |
| | | outputs.value = items || []; |
| | | warehouses.value = (list || []).filter(w => ['åºæä»', 'åºæ'].includes(w.warehouseType || '') && !w.frozen); |
| | | if (!outputs.value.length) message.warning('è¯¥è®¢åæ²¡æå·²å®æç产åºç©æ'); |
| | | if (!warehouses.value.length) message.warning('请å
å¨ä»åºè®¾ç½®ä¸å°åºæä»çä»åºç±»å设为âåºæä»â'); |
| | | } finally { busy.value = false; } |
| | | } |
| | | |
| | | async function submit() { |
| | | if (!orderId.value || !itemId.value || !quantity.value || quantity.value <= 0 || !warehouseId.value || !locationId.value || !areaId.value) { |
| | | message.warning('è¯·éæ©äº§åºç©æãæ°é以ååºæä»åºååºä½'); return; |
| | | } |
| | | busy.value = true; |
| | | try { |
| | | await requestClient.post('/mes/wm/scrap-receipt/create-from-output', { |
| | | requestId: requestId.value, workOrderId: orderId.value, itemId: itemId.value, |
| | | quantity: quantity.value, warehouseId: warehouseId.value, locationId: locationId.value, |
| | | areaId: areaId.value, batchId: batchId.value, batchCode: batchCode.value || undefined, |
| | | }); |
| | | message.success('åºæå·²å
¥åº'); visible.value = false; emit('success'); |
| | | } finally { busy.value = false; } |
| | | } |
| | | defineExpose({ open }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal v-model:open="visible" title="åºæå
¥åº" :confirm-loading="busy" :mask-closable="false" @ok="submit"> |
| | | <Form layout="vertical"> |
| | | <FormItem label="ç产订å"><Input :value="orderCode" disabled /></FormItem> |
| | | <FormItem label="产åºç©æ" required><Select v-model:value="selectedOutput" :options="options" placeholder="è¯·éæ©è®¢å产åºç©æ" @change="selectOutput" /></FormItem> |
| | | <FormItem label="åºææ°é" required><InputNumber v-model:value="quantity" :min="0.0001" :precision="4" class="w-full" /></FormItem> |
| | | <FormItem label="åºæä»åº" required><Select v-model:value="warehouseId" :options="warehouses.map(w => ({value: w.id, label: w.name}))" placeholder="è¯·éæ©åºæä»åº" @change="locationId = undefined; areaId = undefined" /></FormItem> |
| | | <FormItem label="åºåº" required><WmWarehouseLocationSelect v-model="locationId" :warehouse-id="warehouseId" @change="areaId = undefined" /></FormItem> |
| | | <FormItem label="åºä½" required><WmWarehouseAreaSelect v-model="areaId" :location-id="locationId" /></FormItem> |
| | | <FormItem label="æ¹æ¬¡å·"><Input :value="batchCode || 'æ '" disabled /></FormItem> |
| | | </Form> |
| | | <template #footer><Button @click="visible = false">åæ¶</Button><Button type="primary" :loading="busy" :disabled="busy" @click="submit">确认å
¥åº</Button></template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, watch } from 'vue'; |
| | | import { Button, Empty, Space, Table } from 'ant-design-vue'; |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | const props = defineProps<{ workOrderId?: number; itemId?: number }>(); |
| | | interface TechnicalFile { |
| | | id: number; |
| | | fileName?: string; |
| | | versionNo?: number; |
| | | previewURL?: string; |
| | | downloadURL?: string; |
| | | } |
| | | const files = ref<TechnicalFile[]>([]); |
| | | const loading = ref(false); |
| | | watch(() => [props.workOrderId, props.itemId] as const, async ([workOrderId, itemId], _old, onCleanup) => { |
| | | let active = true; |
| | | onCleanup(() => { active = false; }); |
| | | files.value = []; |
| | | if (!workOrderId && !itemId) { |
| | | loading.value = false; |
| | | return; |
| | | } |
| | | loading.value = true; |
| | | try { |
| | | const data: TechnicalFile[] = workOrderId |
| | | ? await requestClient.get('/mes/pro/work-order/technical-files', { params: { id: workOrderId } }) |
| | | : await requestClient.get('/mdm/item/technical-file/list', { params: { itemId } }); |
| | | if (active) files.value = data || []; |
| | | } finally { |
| | | if (active) loading.value = false; |
| | | } |
| | | }, { immediate: true }); |
| | | </script> |
| | | |
| | | <template> |
| | | <p class="mb-3 text-xs text-gray-500">{{ workOrderId ? 'æ¾ç¤ºç产订åå建æ¶çåçææ¯æä»¶ã' : 'æ¾ç¤ºç©æå½åææ¯æä»¶ï¼ä¸åæ¶ä¿åå°ç产订åã' }}é¢è§åå¯ä½¿ç¨æµè§å¨æå°ï¼å
¶ä»æ ¼å¼å¯ä¸è½½åæå°ã</p> |
| | | <Table v-if="files.length || loading" :data-source="files" :loading="loading" :pagination="false" row-key="id" size="small"> |
| | | <Table.Column title="æä»¶åç§°" data-index="fileName" /> |
| | | <Table.Column title="æä»¶çæ¬" data-index="versionNo" /> |
| | | <Table.Column title="æä½" key="actions"> |
| | | <template #default="{ record }"> |
| | | <Space> |
| | | <Button v-if="record.previewURL" type="link" :href="record.previewURL" target="_blank" rel="noopener noreferrer">é¢è§ / æå°</Button> |
| | | <Button v-if="record.downloadURL" type="link" :href="record.downloadURL" target="_blank" rel="noopener noreferrer">ä¸è½½</Button> |
| | | </Space> |
| | | </template> |
| | | </Table.Column> |
| | | </Table> |
| | | <Empty v-else :description="workOrderId ? 'è¯¥å·¥åæ²¡æçåææ¯æä»¶' : 'å½åç©ææ²¡æææ¯æä»¶'" /> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | import { useAccess } from '@vben/access'; |
| | | import { Button, Empty, message, Popconfirm, Space, Table, Upload } from 'ant-design-vue'; |
| | | import { requestClient } from '#/api/request'; |
| | | import { uploadFile, type SystemStorageApi } from '#/api/system/storage'; |
| | | |
| | | const props = defineProps<{ qcType: 'iqc' | 'ipqc' | 'oqc' | 'rqc'; qcId: number; readonly?: boolean }>(); |
| | | const { hasAccessByCodes, hasAccessByRoles } = useAccess(); |
| | | const canEdit = computed(() => !props.readonly && (hasAccessByRoles(['super_admin']) || hasAccessByCodes([`mes:qc-${props.qcType}:update`]))); |
| | | const files = ref<SystemStorageApi.StorageBlob[]>([]); |
| | | const loading = ref(false); |
| | | async function refresh() { |
| | | files.value = await requestClient.get('/mes/qc/attachment/list', { params: { type: props.qcType, id: props.qcId } }); |
| | | } |
| | | onMounted(refresh); |
| | | async function beforeUpload(file: File) { |
| | | if (loading.value) return Upload.LIST_IGNORE; |
| | | loading.value = true; |
| | | try { |
| | | const uploaded = await uploadFile([file]); |
| | | const blobId = uploaded?.[0]?.id; |
| | | if (!blobId) throw new Error('ä¸ä¼ ç»æç¼ºå°æä»¶ç¼å·'); |
| | | await requestClient.post('/mes/qc/attachment/append', { |
| | | type: props.qcType, id: props.qcId, blobId, |
| | | }); |
| | | await refresh(); |
| | | message.success('éä»¶å·²ä¸ä¼ '); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | return Upload.LIST_IGNORE; |
| | | } |
| | | async function removeFile(blobId: number) { |
| | | loading.value = true; |
| | | try { |
| | | await requestClient.delete('/mes/qc/attachment/remove', { params: { type: props.qcType, id: props.qcId, blobId } }); |
| | | await refresh(); |
| | | message.success('é件已移é¤'); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Space direction="vertical" class="w-full"> |
| | | <Upload v-if="canEdit" :show-upload-list="false" :before-upload="beforeUpload" :disabled="loading"> |
| | | <Button :loading="loading">ä¸ä¼ æ£éªéä»¶</Button> |
| | | </Upload> |
| | | <Table v-if="files.length" :data-source="files" :pagination="false" row-key="id" size="small"> |
| | | <Table.Column title="æä»¶åç§°" data-index="name" /> |
| | | <Table.Column title="æä½" key="actions"> |
| | | <template #default="{ record }"> |
| | | <a v-if="record.previewURL || record.downloadURL" :href="record.previewURL || record.downloadURL" target="_blank" rel="noopener noreferrer">æ¥ç / ä¸è½½</a> |
| | | <Popconfirm v-if="canEdit" title="确认ä»è´¨æ£åç§»é¤æ¤éä»¶ï¼" ok-text="ç§»é¤" cancel-text="åæ¶" @confirm="removeFile(record.id)"> |
| | | <a class="ml-3 text-red-500">ç§»é¤</a> |
| | | </Popconfirm> |
| | | </template> |
| | | </Table.Column> |
| | | </Table> |
| | | <Empty v-else description="ææ æ£éªéä»¶" /> |
| | | </Space> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesQcSourceLinkApi } from '#/api/mes/qc/source-link'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useAccess } from '@vben/access'; |
| | | |
| | | import { Button, message, Modal, Table } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | getSourceLinkCandidates, |
| | | linkSource, |
| | | } from '#/api/mes/qc/source-link'; |
| | | |
| | | const props = defineProps<{ |
| | | type: MesQcSourceLinkApi.LinkType; |
| | | qcId: number; |
| | | reelNo?: string; |
| | | }>(); |
| | | |
| | | const emit = defineEmits<{ linked: [] }>(); |
| | | |
| | | const { hasAccessByCodes, hasAccessByRoles } = useAccess(); |
| | | |
| | | const allowed = computed( |
| | | () => |
| | | hasAccessByRoles(['super_admin']) || |
| | | hasAccessByCodes([ |
| | | `mes:qc-${props.type === 'feedback' ? 'ipqc' : 'oqc'}:update`, |
| | | ]), |
| | | ); |
| | | const title = computed(() => |
| | | props.type === 'feedback' ? 'å
³èç产æ¥å·¥' : 'å
³èéå®åºåº', |
| | | ); |
| | | const targetName = computed(() => |
| | | props.type === 'feedback' ? 'å¾
æ£æ¥å·¥äº§åºè¡' : 'å¾
æ£åºåºæç»', |
| | | ); |
| | | |
| | | const open = ref(false); |
| | | const loading = ref(false); |
| | | const linkingLineId = ref<number>(); |
| | | |
| | | const rows = ref<MesQcSourceLinkApi.Candidate[]>([]); |
| | | |
| | | const columns = [ |
| | | { title: 'æ¥æºåæ®ç¼å·', dataIndex: 'documentCode', key: 'documentCode' }, |
| | | { title: 'çå·', dataIndex: 'reelNo', key: 'reelNo' }, |
| | | { title: 'å¾
æ£æ°é', dataIndex: 'quantity', key: 'quantity' }, |
| | | { title: 'æä½', key: 'action', width: 100 }, |
| | | ]; |
| | | |
| | | async function show() { |
| | | if (!props.reelNo?.trim()) { |
| | | message.warning('该质æ£å缺å°çå·ï¼è¯·å
è¡¥å
çå·ååå
³è'); |
| | | return; |
| | | } |
| | | open.value = true; |
| | | loading.value = true; |
| | | try { |
| | | rows.value = await getSourceLinkCandidates(props.type, props.qcId); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | async function link(row: MesQcSourceLinkApi.Candidate) { |
| | | linkingLineId.value = row.lineId; |
| | | try { |
| | | await linkSource(props.type, props.qcId, row.lineId); |
| | | message.success('å
³èæåï¼è´¨æ£æ°éå·²å忥æºåæ®'); |
| | | open.value = false; |
| | | emit('linked'); |
| | | } catch { |
| | | // å端é误æç¤ºå·²ç±è¯·æ±æ¦æªå¨å±ç¤ºï¼ä¿çå¼¹çªä¾¿äºéæ°éæ©åé |
| | | } finally { |
| | | linkingLineId.value = undefined; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Button v-if="allowed" @click="show">{{ title }}</Button> |
| | | <Modal v-model:open="open" :title="title" :footer="null" :width="760"> |
| | | <p class="mb-3"> |
| | | å½åè´¨æ£åçå·ï¼{{ reelNo || 'æªå¡«å' }}ãä»
æ¾ç¤ºç©æãçå·åæ°éåä¸è´ä¸ä»å¤äºå¾
æ£ç¶æç{{ |
| | | targetName |
| | | }}ã |
| | | </p> |
| | | <Table |
| | | :columns="columns" |
| | | :data-source="rows" |
| | | :loading="loading" |
| | | :pagination="{ pageSize: 10 }" |
| | | row-key="lineId" |
| | | size="small" |
| | | > |
| | | <template #emptyText> |
| | | <div class="py-4 text-gray-500"> |
| | | ææ å¯å
³èç{{ targetName }}ï¼è¯·æ ¸å¯¹çå·ãæ£æµæ°é忥æºåæ®ç¶æåéè¯ã |
| | | </div> |
| | | </template> |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.key === 'action'"> |
| | | <Button |
| | | type="link" |
| | | :disabled="!!linkingLineId" |
| | | :loading="linkingLineId === record.lineId" |
| | | @click="link(record as MesQcSourceLinkApi.Candidate)" |
| | | > |
| | | å
³è |
| | | </Button> |
| | | </template> |
| | | </template> |
| | | </Table> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getTemplateItemOptionList } from '#/api/mes/qc/template/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'templateId', |
| | | label: 'è´¨æ£æ¹æ¡', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | placeholder: 'è¯·éæ©è´¨æ£æ¹æ¡', |
| | | }, |
| | | rules: 'selectRequired', |
| | | dependencies: { |
| | | triggerFields: ['workOrderId', 'taskId'], |
| | | componentProps: (values) => ({ |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | disabled: formType !== 'create' || !values.workOrderId, |
| | | params: values.workOrderId |
| | | ? { |
| | | qcType: 2, |
| | | workOrderId: values.workOrderId, |
| | | taskId: values.taskId, |
| | | } |
| | | : {}, |
| | | placeholder: values.workOrderId |
| | | ? 'è¯·éæ©è´¨æ£æ¹æ¡' |
| | | : '请å
éæ©ç产订å', |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'checkQuantity', |
| | | label: 'æ£æµæ°é', |
| | | component: 'InputNumber', |
| | |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_QC_CHECK_RESULT, 'number'), |
| | | placeholder: 'è¯·éæ©æ£æµç»æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcStatusEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createIpqc, finishIpqc, getIpqc, updateIpqc } from '#/api/mes/qc/ipqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { QcIndicatorResultList } from '../../indicatorresult/components'; |
| | | import MesQcAiSuggest from '../../components/ai-suggest.vue'; |
| | | import MesQcAiAssistant from '../../components/ai-assistant.vue'; |
| | | import QcAttachments from '../../components/qc-attachments.vue'; |
| | | import QcSourceLink from '../../components/qc-source-link.vue'; |
| | | import { useFormSchema } from '../data'; |
| | | import LineList from './line-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesQcIpqcApi.Ipqc>(); |
| | | const subTabsName = ref('line'); |
| | | const subTabsName = ref('attachments'); |
| | | const originalSnapshot = ref(''); // 表ååå§æ°æ®å¿«ç
§ï¼ç¨äº finish æ¶è·³è¿æªåæ´çä¿åè¯·æ± |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const canFinish = computed( |
| | |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** åè¡¨åæ´åå·æ°ä¸»è¡¨å¤´æ°æ®ï¼ç¼ºé·ç»è®¡çï¼ */ |
| | | async function handleRefresh() { |
| | | if (!formData.value?.id) { |
| | | return; |
| | | } |
| | | formData.value = await getIpqc(formData.value.id); |
| | | } |
| | | |
| | | /** æäº¤è¡¨å */ |
| | | async function handleSubmit(): Promise<boolean> { |
| | | const { valid } = await formApi.validate(); |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateIpqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** å
³èæ¥æºåæ®åå·æ°è¯¦æ
ãè¡¨åæ¾ç¤ºä¸å¿«ç
§ */ |
| | | async function handleLinked() { |
| | | const id = formData.value?.id; |
| | | if (!id) { |
| | | return; |
| | | } |
| | | formData.value = await getIpqc(id); |
| | | await formApi.setValues(formData.value); |
| | | originalSnapshot.value = JSON.stringify(await formApi.getValues()); |
| | | emit('success'); |
| | | } |
| | | |
| | | /** 宿æ£éªåï¼è¡¨åæä¿®æ¹æ¶å
ä¿åï¼åè°ç¨å®ææ¥å£ */ |
| | | async function handleFinish() { |
| | | const id = formData.value?.id; |
| | |
| | | return; |
| | | } |
| | | try { |
| | | await confirm('æ¯å¦å®æè¿ç¨æ£éªåç¼å¶ï¼ã宿åå°ä¸è½æ´æ¹ã'); |
| | | await confirm('æ¯å¦å®æè¿ç¨æ£éªåç¼å¶ï¼ã宿åä»
å¯ä¿®æ¹æ£éªéä»¶ã'); |
| | | } catch { |
| | | return; |
| | | } |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcIpqcApi.Ipqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateIpqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | originalSnapshot.value = ''; |
| | | return; |
| | | } |
| | | subTabsName.value = 'line'; |
| | | subTabsName.value = 'attachments'; |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | formType: FormType; |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- 缺é·ç»è®¡ï¼åªè¯»ï¼ --> |
| | | <div v-if="formData?.id" class="mx-4 mt-4"> |
| | | <Descriptions title="ç¼ºé·æ
åµ" :column="3" bordered size="small"> |
| | | <Descriptions.Item label="è´å½ç¼ºé·æ°"> |
| | | {{ formData.criticalQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严éç¼ºé·æ°"> |
| | | {{ formData.majorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è½»å¾®ç¼ºé·æ°"> |
| | | {{ formData.minorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è´å½ç¼ºé·ç"> |
| | | {{ formData.criticalRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严é缺é·ç"> |
| | | {{ formData.majorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="轻微缺é·ç"> |
| | | {{ formData.minorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | </div> |
| | | <Tabs |
| | | v-if="formData?.id" |
| | | v-model:active-key="subTabsName" |
| | | class="mx-4 mt-4" |
| | | > |
| | | <Tabs.TabPane key="line" tab="æ£éªé¡¹"> |
| | | <LineList |
| | | :form-type="formType" |
| | | :ipqc-id="formData.id" |
| | | @refresh="handleRefresh" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="result" tab="æ£æµç»æ"> |
| | | <QcIndicatorResultList |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.IPQC" |
| | | :readonly="isDetail" |
| | | /> |
| | | <Tabs.TabPane key="attachments" tab="æ£éªéä»¶"> |
| | | <QcAttachments qc-type="ipqc" :qc-id="formData!.id!" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | <template #prepend-footer> |
| | | <div class="flex flex-auto items-center gap-2"> |
| | | <QcSourceLink |
| | | v-if=" |
| | | formData?.id && |
| | | formData.status === MesQcStatusEnum.FINISHED && |
| | | !formData.sourceDocId |
| | | " |
| | | :qc-id="formData.id" |
| | | :reel-no="formData.reelNo" |
| | | type="feedback" |
| | | @linked="handleLinked" |
| | | /> |
| | | <Button v-if="canFinish" type="primary" @click="handleFinish"> |
| | | 宿 |
| | | </Button> |
| | | <MesQcAiSuggest |
| | | v-if="canFinish && formData?.id" |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.IPQC" |
| | | /> |
| | | <MesQcAiAssistant /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getTemplateItemOptionList } from '#/api/mes/qc/template/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'templateId', |
| | | label: 'è´¨æ£æ¹æ¡', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | placeholder: 'è¯·éæ©è´¨æ£æ¹æ¡', |
| | | }, |
| | | rules: 'selectRequired', |
| | | dependencies: { |
| | | triggerFields: ['itemId'], |
| | | componentProps: (values) => ({ |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | disabled: formType !== 'create' || !values.itemId, |
| | | params: values.itemId |
| | | ? { itemId: values.itemId, qcType: 1 } |
| | | : {}, |
| | | placeholder: values.itemId ? 'è¯·éæ©è´¨æ£æ¹æ¡' : '请å
éæ©äº§åç©æ', |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'vendorName', |
| | | label: 'ä¾åºå', |
| | | component: 'Input', |
| | |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_QC_CHECK_RESULT, 'number'), |
| | | placeholder: 'è¯·éæ©æ£æµç»æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcStatusEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createIqc, finishIqc, getIqc, updateIqc } from '#/api/mes/qc/iqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { QcIndicatorResultList } from '../../indicatorresult/components'; |
| | | import MesQcAiSuggest from '../../components/ai-suggest.vue'; |
| | | import MesQcAiAssistant from '../../components/ai-assistant.vue'; |
| | | import QcAttachments from '../../components/qc-attachments.vue'; |
| | | import { useFormSchema } from '../data'; |
| | | import LineList from './line-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesQcIqcApi.Iqc>(); |
| | | const subTabsName = ref('line'); |
| | | const subTabsName = ref('attachments'); |
| | | const originalSnapshot = ref(''); // 表ååå§æ°æ®å¿«ç
§ï¼ç¨äº finish æ¶è·³è¿æªåæ´çä¿åè¯·æ± |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const canFinish = computed( |
| | |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** åè¡¨åæ´åå·æ°ä¸»è¡¨å¤´æ°æ®ï¼ç¼ºé·ç»è®¡çï¼ */ |
| | | async function handleRefresh() { |
| | | if (!formData.value?.id) { |
| | | return; |
| | | } |
| | | formData.value = await getIqc(formData.value.id); |
| | | } |
| | | |
| | | /** æäº¤è¡¨å */ |
| | | async function handleSubmit(): Promise<boolean> { |
| | | const { valid } = await formApi.validate(); |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcIqcApi.Iqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateIqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | return; |
| | | } |
| | | try { |
| | | await confirm('æ¯å¦å®ææ¥ææ£éªåç¼å¶ï¼ã宿åå°ä¸è½æ´æ¹ã'); |
| | | await confirm('æ¯å¦å®ææ¥ææ£éªåç¼å¶ï¼ã宿åä»
å¯ä¿®æ¹æ£éªéä»¶ã'); |
| | | } catch { |
| | | return; |
| | | } |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcIqcApi.Iqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateIqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | originalSnapshot.value = ''; |
| | | return; |
| | | } |
| | | subTabsName.value = 'line'; |
| | | subTabsName.value = 'attachments'; |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | formType: FormType; |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- 缺é·ç»è®¡ï¼åªè¯»ï¼ --> |
| | | <div v-if="formData?.id" class="mx-4 mt-4"> |
| | | <Descriptions title="ç¼ºé·æ
åµ" :column="3" bordered size="small"> |
| | | <Descriptions.Item label="è´å½ç¼ºé·æ°"> |
| | | {{ formData.criticalQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严éç¼ºé·æ°"> |
| | | {{ formData.majorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è½»å¾®ç¼ºé·æ°"> |
| | | {{ formData.minorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è´å½ç¼ºé·ç"> |
| | | {{ formData.criticalRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严é缺é·ç"> |
| | | {{ formData.majorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="轻微缺é·ç"> |
| | | {{ formData.minorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | </div> |
| | | <Tabs |
| | | v-if="formData?.id" |
| | | v-model:active-key="subTabsName" |
| | | class="mx-4 mt-4" |
| | | > |
| | | <Tabs.TabPane key="line" tab="æ£éªé¡¹"> |
| | | <LineList |
| | | :form-type="formType" |
| | | :iqc-id="formData.id" |
| | | @refresh="handleRefresh" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="result" tab="æ£æµç»æ"> |
| | | <QcIndicatorResultList |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.IQC" |
| | | :readonly="isDetail" |
| | | /> |
| | | <Tabs.TabPane key="attachments" tab="æ£éªéä»¶"> |
| | | <QcAttachments qc-type="iqc" :qc-id="formData!.id!" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | <template #prepend-footer> |
| | |
| | | <Button v-if="canFinish" type="primary" @click="handleFinish"> |
| | | 宿 |
| | | </Button> |
| | | <MesQcAiSuggest |
| | | v-if="canFinish && formData?.id" |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.IQC" |
| | | /> |
| | | <MesQcAiAssistant /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getTemplateItemOptionList } from '#/api/mes/qc/template/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import CrmCustomerSelect from '#/components/crm-customer-select.vue'; |
| | |
| | | allCustomers: true, |
| | | disabled: !!values.sourceDocId, |
| | | placeholder: 'è¯·éæ©äº§åç©æ', |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'templateId', |
| | | label: 'è´¨æ£æ¹æ¡', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | placeholder: 'è¯·éæ©è´¨æ£æ¹æ¡', |
| | | }, |
| | | rules: 'selectRequired', |
| | | dependencies: { |
| | | triggerFields: ['itemId'], |
| | | componentProps: (values) => ({ |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | disabled: formType !== 'create' || !values.itemId, |
| | | params: values.itemId |
| | | ? { itemId: values.itemId, qcType: 3 } |
| | | : {}, |
| | | placeholder: values.itemId ? 'è¯·éæ©è´¨æ£æ¹æ¡' : '请å
éæ©äº§åç©æ', |
| | | }), |
| | | }, |
| | | }, |
| | |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_QC_CHECK_RESULT, 'number'), |
| | | placeholder: 'è¯·éæ©æ£æµç»æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcStatusEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createOqc, finishOqc, getOqc, updateOqc } from '#/api/mes/qc/oqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { QcIndicatorResultList } from '../../indicatorresult/components'; |
| | | import MesQcAiSuggest from '../../components/ai-suggest.vue'; |
| | | import MesQcAiAssistant from '../../components/ai-assistant.vue'; |
| | | import QcAttachments from '../../components/qc-attachments.vue'; |
| | | import QcSourceLink from '../../components/qc-source-link.vue'; |
| | | import { useFormSchema } from '../data'; |
| | | import LineList from './line-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesQcOqcApi.Oqc>(); |
| | | const subTabsName = ref('line'); |
| | | const subTabsName = ref('attachments'); |
| | | const originalSnapshot = ref(''); // 表ååå§æ°æ®å¿«ç
§ï¼ç¨äº finish æ¶è·³è¿æªåæ´çä¿åè¯·æ± |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const canFinish = computed( |
| | |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** åè¡¨åæ´åå·æ°ä¸»è¡¨å¤´æ°æ®ï¼ç¼ºé·ç»è®¡çï¼ */ |
| | | async function handleRefresh() { |
| | | if (!formData.value?.id) { |
| | | return; |
| | | } |
| | | formData.value = await getOqc(formData.value.id); |
| | | } |
| | | |
| | | /** æäº¤è¡¨å */ |
| | | async function handleSubmit(): Promise<boolean> { |
| | | const { valid } = await formApi.validate(); |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcOqcApi.Oqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateOqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** å
³èæ¥æºåæ®åå·æ°è¯¦æ
ãè¡¨åæ¾ç¤ºä¸å¿«ç
§ */ |
| | | async function handleLinked() { |
| | | const id = formData.value?.id; |
| | | if (!id) { |
| | | return; |
| | | } |
| | | formData.value = await getOqc(id); |
| | | await formApi.setValues(formData.value); |
| | | originalSnapshot.value = JSON.stringify(await formApi.getValues()); |
| | | emit('success'); |
| | | } |
| | | |
| | | /** 宿æ£éªåï¼è¡¨åæä¿®æ¹æ¶å
ä¿åï¼åè°ç¨å®ææ¥å£ */ |
| | | async function handleFinish() { |
| | | const id = formData.value?.id; |
| | |
| | | return; |
| | | } |
| | | try { |
| | | await confirm('æ¯å¦å®æåºè´§æ£éªåç¼å¶ï¼ã宿åå°ä¸è½æ´æ¹ã'); |
| | | await confirm('æ¯å¦å®æåºè´§æ£éªåç¼å¶ï¼ã宿åä»
å¯ä¿®æ¹æ£éªéä»¶ã'); |
| | | } catch { |
| | | return; |
| | | } |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcOqcApi.Oqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateOqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | originalSnapshot.value = ''; |
| | | return; |
| | | } |
| | | subTabsName.value = 'line'; |
| | | subTabsName.value = 'attachments'; |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | formType: FormType; |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- 缺é·ç»è®¡ï¼åªè¯»ï¼ --> |
| | | <div v-if="formData?.id" class="mx-4 mt-4"> |
| | | <Descriptions title="ç¼ºé·æ
åµ" :column="3" bordered size="small"> |
| | | <Descriptions.Item label="è´å½ç¼ºé·æ°"> |
| | | {{ formData.criticalQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严éç¼ºé·æ°"> |
| | | {{ formData.majorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è½»å¾®ç¼ºé·æ°"> |
| | | {{ formData.minorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è´å½ç¼ºé·ç"> |
| | | {{ formData.criticalRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严é缺é·ç"> |
| | | {{ formData.majorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="轻微缺é·ç"> |
| | | {{ formData.minorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | </div> |
| | | <Tabs |
| | | v-if="formData?.id" |
| | | v-model:active-key="subTabsName" |
| | | class="mx-4 mt-4" |
| | | > |
| | | <Tabs.TabPane key="line" tab="æ£éªé¡¹"> |
| | | <LineList |
| | | :form-type="formType" |
| | | :oqc-id="formData.id" |
| | | @refresh="handleRefresh" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="result" tab="æ£æµç»æ"> |
| | | <QcIndicatorResultList |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.OQC" |
| | | :readonly="isDetail" |
| | | /> |
| | | <Tabs.TabPane key="attachments" tab="æ£éªéä»¶"> |
| | | <QcAttachments qc-type="oqc" :qc-id="formData!.id!" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | <template #prepend-footer> |
| | | <div class="flex flex-auto items-center gap-2"> |
| | | <QcSourceLink |
| | | v-if=" |
| | | formData?.id && |
| | | formData.status === MesQcStatusEnum.FINISHED && |
| | | !formData.sourceDocId |
| | | " |
| | | :qc-id="formData.id" |
| | | :reel-no="formData.reelNo" |
| | | type="sales" |
| | | @linked="handleLinked" |
| | | /> |
| | | <Button v-if="canFinish" type="primary" @click="handleFinish"> |
| | | 宿 |
| | | </Button> |
| | | <MesQcAiSuggest |
| | | v-if="canFinish && formData?.id" |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.OQC" |
| | | /> |
| | | <MesQcAiAssistant /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getTemplateItemOptionList } from '#/api/mes/qc/template/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'templateId', |
| | | label: 'è´¨æ£æ¹æ¡', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | placeholder: 'è¯·éæ©è´¨æ£æ¹æ¡', |
| | | }, |
| | | rules: 'selectRequired', |
| | | dependencies: { |
| | | triggerFields: ['itemId'], |
| | | componentProps: (values) => ({ |
| | | api: getTemplateItemOptionList, |
| | | autoSelect: 'first', |
| | | labelField: 'templateName', |
| | | valueField: 'templateId', |
| | | disabled: formType !== 'create' || !values.itemId, |
| | | params: values.itemId |
| | | ? { itemId: values.itemId, qcType: 4 } |
| | | : {}, |
| | | placeholder: values.itemId ? 'è¯·éæ©è´¨æ£æ¹æ¡' : '请å
éæ©äº§åç©æ', |
| | | }), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'batchCode', |
| | | label: 'æ¹æ¬¡å·', |
| | | component: 'Input', |
| | |
| | | valueField: 'id', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'checkResult', |
| | | label: 'æ£æµç»æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_QC_CHECK_RESULT, 'number'), |
| | | placeholder: 'è¯·éæ©æ£æµç»æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { MesQcCheckResultEnum, MesQcStatusEnum, MesQcTypeEnum } from '@vben/constants'; |
| | | import { MesQcStatusEnum } from '@vben/constants'; |
| | | |
| | | import { Button, Descriptions, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { createRqc, finishRqc, getRqc, updateRqc } from '#/api/mes/qc/rqc'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { QcIndicatorResultList } from '../../indicatorresult/components'; |
| | | import MesQcAiSuggest from '../../components/ai-suggest.vue'; |
| | | import MesQcAiAssistant from '../../components/ai-assistant.vue'; |
| | | import QcAttachments from '../../components/qc-attachments.vue'; |
| | | import { useFormSchema } from '../data'; |
| | | import LineList from './line-list.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesQcRqcApi.Rqc>(); |
| | | const subTabsName = ref('line'); |
| | | const subTabsName = ref('attachments'); |
| | | const originalSnapshot = ref(''); // 表ååå§æ°æ®å¿«ç
§ï¼ç¨äº finish æ¶è·³è¿æªåæ´çä¿åè¯·æ± |
| | | const isDetail = computed(() => formType.value === 'detail'); |
| | | const canFinish = computed( |
| | |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** åè¡¨åæ´åå·æ°ä¸»è¡¨å¤´æ°æ®ï¼ç¼ºé·ç»è®¡çï¼ */ |
| | | async function handleRefresh() { |
| | | if (!formData.value?.id) { |
| | | return; |
| | | } |
| | | formData.value = await getRqc(formData.value.id); |
| | | } |
| | | |
| | | /** æäº¤è¡¨å */ |
| | | async function handleSubmit(): Promise<boolean> { |
| | | const { valid } = await formApi.validate(); |
| | |
| | | return false; |
| | | } |
| | | const data = (await formApi.getValues()) as MesQcRqcApi.Rqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return false; |
| | | } |
| | | if (formData.value?.id) { |
| | | await updateRqc({ ...data, id: formData.value.id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | |
| | | return; |
| | | } |
| | | try { |
| | | await confirm('æ¯å¦å®æéè´§æ£éªåç¼å¶ï¼ã宿åå°ä¸è½æ´æ¹ã'); |
| | | await confirm('æ¯å¦å®æéè´§æ£éªåç¼å¶ï¼ã宿åä»
å¯ä¿®æ¹æ£éªéä»¶ã'); |
| | | } catch { |
| | | return; |
| | | } |
| | |
| | | const current = JSON.stringify(await formApi.getValues()); |
| | | if (current !== originalSnapshot.value) { |
| | | const data = (await formApi.getValues()) as MesQcRqcApi.Rqc; |
| | | if ( |
| | | (data.unqualifiedQuantity == null || data.unqualifiedQuantity === 0) && |
| | | data.checkResult === MesQcCheckResultEnum.FAIL |
| | | ) { |
| | | message.warning('ä¸åæ ¼åæ°é为0æ¶ï¼æ£æµç»æä¸è½ä¸º"æ ¡éªä¸éè¿"'); |
| | | return; |
| | | } |
| | | await updateRqc({ ...data, id }); |
| | | formData.value = { ...formData.value, ...data }; |
| | | originalSnapshot.value = current; |
| | |
| | | originalSnapshot.value = ''; |
| | | return; |
| | | } |
| | | subTabsName.value = 'line'; |
| | | subTabsName.value = 'attachments'; |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ |
| | | formType: FormType; |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-4/5"> |
| | | <Form class="mx-4" /> |
| | | <!-- 缺é·ç»è®¡ï¼åªè¯»ï¼ --> |
| | | <div v-if="formData?.id" class="mx-4 mt-4"> |
| | | <Descriptions title="ç¼ºé·æ
åµ" :column="3" bordered size="small"> |
| | | <Descriptions.Item label="è´å½ç¼ºé·æ°"> |
| | | {{ formData.criticalQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严éç¼ºé·æ°"> |
| | | {{ formData.majorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è½»å¾®ç¼ºé·æ°"> |
| | | {{ formData.minorQuantity ?? 0 }} |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="è´å½ç¼ºé·ç"> |
| | | {{ formData.criticalRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="严é缺é·ç"> |
| | | {{ formData.majorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | <Descriptions.Item label="轻微缺é·ç"> |
| | | {{ formData.minorRate ?? 0 }}% |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | </div> |
| | | <Tabs |
| | | v-if="formData?.id" |
| | | v-model:active-key="subTabsName" |
| | | class="mx-4 mt-4" |
| | | > |
| | | <Tabs.TabPane key="line" tab="æ£éªé¡¹"> |
| | | <LineList |
| | | :form-type="formType" |
| | | :rqc-id="formData.id" |
| | | @refresh="handleRefresh" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane key="result" tab="æ£æµç»æ"> |
| | | <QcIndicatorResultList |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.RQC" |
| | | :readonly="isDetail" |
| | | /> |
| | | <Tabs.TabPane key="attachments" tab="æ£éªéä»¶"> |
| | | <QcAttachments qc-type="rqc" :qc-id="formData!.id!" /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | <template #prepend-footer> |
| | |
| | | <Button v-if="canFinish" type="primary" @click="handleFinish"> |
| | | 宿 |
| | | </Button> |
| | | <MesQcAiSuggest |
| | | v-if="canFinish && formData?.id" |
| | | :qc-id="formData.id" |
| | | :qc-type="MesQcTypeEnum.RQC" |
| | | /> |
| | | <MesQcAiAssistant /> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Input, Modal, Spin } from 'ant-design-vue'; |
| | | import { Input, message, Modal, Spin } from 'ant-design-vue'; |
| | | |
| | | import { getWorkstationPage } from '#/api/mes/md/workstation'; |
| | | import { getUnfinishedWorkstationCounts } from '#/api/mes/pro/task'; |
| | | |
| | | defineOptions({ name: 'WorkbenchWorkstationSelectDialog' }); |
| | | |
| | |
| | | const loading = ref(false); |
| | | const workstations = ref<MesMdWorkstationApi.Workstation[]>([]); |
| | | const searchKeyword = ref(''); |
| | | const pendingCounts = ref<Record<number, number>>({}); |
| | | const countsAvailable = ref(false); |
| | | |
| | | /** æå¼å¼¹çª */ |
| | | async function open() { |
| | |
| | | async function loadWorkstations() { |
| | | loading.value = true; |
| | | try { |
| | | const result = await getWorkstationPage({ pageNo: 1, pageSize: 200 }); |
| | | pendingCounts.value = {}; |
| | | countsAvailable.value = false; |
| | | const [result, counts] = await Promise.all([ |
| | | getWorkstationPage({ pageNo: 1, pageSize: 200 }), |
| | | getUnfinishedWorkstationCounts().catch(() => { |
| | | message.warning('æªè½è·åå·¥ä½ç«ä»»å¡æ°é'); |
| | | return null; |
| | | }), |
| | | ]); |
| | | workstations.value = result.list ?? []; |
| | | if (counts) { |
| | | pendingCounts.value = Object.fromEntries(counts.map((item) => [item.workstationId, item.pendingCount])); |
| | | countsAvailable.value = true; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | |
| | | </span> |
| | | </div> |
| | | <div class="ws-card__name">{{ ws.name }}</div> |
| | | <div class="ws-card__info-row">æªå®æä»»å¡ï¼{{ countsAvailable ? (pendingCounts[ws.id!] ?? 0) : 'â' }} æ¡</div> |
| | | <div class="ws-card__info"> |
| | | <div v-if="ws.workshopName" class="ws-card__info-row"> |
| | | <IconifyIcon icon="lucide:building-2" class="ws-card__info-icon" /> |
| | |
| | | cancelProductIssue, |
| | | createProductIssue, |
| | | deleteProductIssue, |
| | | getProductIssue, |
| | | getProductIssuePage, |
| | | submitProductIssue, |
| | | updateProductIssue, |
| | | } from '#/api/mes/wm/productissue'; |
| | | import { getProductIssueLinesByIssue } from '#/api/mes/wm/productissue/line'; |
| | | import dayjs from 'dayjs'; |
| | | import { MdWorkstationSelect } from '#/views/mes/md/workstation/components'; |
| | | import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; |
| | | |
| | |
| | | props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS }, |
| | | }, |
| | | }, |
| | | { title: 'æä½', width: 260, fixed: 'right', slots: { default: 'actions' } }, |
| | | { title: 'æä½', width: 360, fixed: 'right', slots: { default: 'actions' } }, |
| | | ]; |
| | | |
| | | const gridHeight = ref(300); |
| | |
| | | /** æ¯å¦å·²éä¸å·¥ä½ç«åä»»å¡ï¼æªé䏿¶ä¸å±ç¤ºè¡¨æ ¼ */ |
| | | const isReady = computed(() => !!props.taskId && !!props.workstationId); |
| | | |
| | | /** åæ®å
容å
¨é¨æææ¬è½¬ä¹ï¼é¿å
ç©æåç§°æå¤æ³¨å½±åæå°é¡µé¢ç»æã */ |
| | | function escapePrintText(value: unknown) { |
| | | return String(value ?? '').replace(/[&<>"']/g, (char) => ({ |
| | | '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', |
| | | })[char] || char); |
| | | } |
| | | |
| | | async function handlePrintIssue(row: MesWmProductIssueApi.ProductIssue) { |
| | | if (!row.id) return; |
| | | // å
æå¼çªå£ï¼é¿å
弿¥è¯·æ±ç»æå被æµè§å¨ä½ä¸ºå¼¹çªæ¦æªã |
| | | const popup = window.open('', '_blank'); |
| | | if (!popup) { message.warning('æµè§å¨æ¦æªäºæå°çªå£ï¼è¯·å
许弹çªåéè¯'); return; } |
| | | popup.document.title = 'æ£å¨åå¤é¢æå'; |
| | | popup.document.body.textContent = 'æ£å¨åå¤é¢æåâ¦'; |
| | | try { |
| | | const [issue, lines] = await Promise.all([getProductIssue(row.id), getProductIssueLinesByIssue(row.id)]); |
| | | if (!issue) throw new Error('颿åä¸åå¨'); |
| | | const safe = escapePrintText; |
| | | const date = (value: unknown) => value && dayjs(value as string | number | Date).isValid() |
| | | ? dayjs(value as string | number | Date).format('YYYY-MM-DD HH:mm') : ''; |
| | | const statusNames: Record<number, string> = { |
| | | [MesWmProductIssueStatusEnum.PREPARE]: 'è稿', |
| | | [MesWmProductIssueStatusEnum.APPROVING]: 'å¾
æ£è´§', |
| | | [MesWmProductIssueStatusEnum.APPROVED]: 'å¾
é¢åº', |
| | | [MesWmProductIssueStatusEnum.FINISHED]: '已宿', |
| | | [MesWmProductIssueStatusEnum.CANCELED]: '已忶', |
| | | }; |
| | | const rows = lines.map((item, index) => `<tr><td>${index + 1}</td><td>${safe(item.itemCode)}</td><td>${safe(item.itemName)}</td><td>${safe(item.specification)}</td><td>${safe(item.unitMeasureName)}</td><td>${safe(item.quantity)}</td><td>${safe(item.remark)}</td></tr>`).join(''); |
| | | popup.document.open(); |
| | | popup.document.write(`<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><title>颿å ${safe(issue.code)}</title><style> |
| | | @page { size: A4; margin: 15mm; } body { color: #111; font: 13px 'Microsoft YaHei', sans-serif; margin: 0 auto; max-width: 180mm; } |
| | | h1 { text-align: center; font-size: 22px; margin: 12px 0 20px; } .meta { display: grid; grid-template-columns: 1fr 1fr; gap: 9px 16px; margin-bottom: 16px; overflow-wrap: anywhere; } |
| | | table { border-collapse: collapse; width: 100%; table-layout: fixed; } th,td { border: 1px solid #555; padding: 7px 4px; word-break: break-word; text-align: left; } th:first-child,td:first-child { width: 32px; text-align: center; } th:nth-child(5),td:nth-child(5) { width: 42px; } th:nth-child(6),td:nth-child(6) { width: 56px; } thead { display: table-header-group; } tr { break-inside: avoid; } |
| | | .sign { display: flex; justify-content: space-between; gap: 18px; margin-top: 32px; } .toolbar { text-align: right; padding: 12px 0; } button { padding: 7px 14px; cursor: pointer; } @media print { .toolbar { display: none; } } |
| | | </style></head><body><div class="toolbar"><button onclick="window.print()">æå° / ä¿å为 PDF</button></div><h1>颿å</h1> |
| | | <div class="meta"><div>颿åå·ï¼${safe(issue.code)}</div><div>颿ååç§°ï¼${safe(issue.name)}</div><div>ç产任å¡ï¼${safe(issue.taskCode)}</div><div>ç产订åï¼${safe(issue.workOrderCode)}</div><div>å·¥ä½ç«ï¼${safe(issue.workstationName || issue.workstationCode)}</div><div>éæ±æ¶é´ï¼${safe(date(issue.requiredTime))}</div><div>ç¶æï¼${safe(statusNames[issue.status ?? -1] ?? 'æªç¥')}</div><div>夿³¨ï¼${safe(issue.remark)}</div></div> |
| | | <table><thead><tr><th>åºå·</th><th>ç©æç¼ç </th><th>ç©æåç§°</th><th>è§æ ¼åå·</th><th>åä½</th><th>ç³è¯·æ°é</th><th>夿³¨</th></tr></thead><tbody>${rows || '<tr><td colspan="7">ææ é¢ææç»</td></tr>'}</tbody></table> |
| | | <div class="sign"><span>ç³è¯·äººï¼____________</span><span>åæäººï¼____________</span><span>é¢æäººï¼____________</span><span>æ¥æï¼____________</span></div></body></html>`); |
| | | popup.document.close(); |
| | | } catch (error) { |
| | | popup.close(); |
| | | message.error('æ æ³çæé¢æåï¼è¯·ç¨åéè¯'); |
| | | console.error(error); |
| | | } |
| | | } |
| | | |
| | | /** ç¼è¾å¼¹çª */ |
| | | const [EditFormModal, editFormModalApi] = useVbenModal({ |
| | | connectedComponent: ProductIssueFormModal, |
| | |
| | | <div ref="listRef" class="issue-tab__list"> |
| | | <Grid table-title="é¢æè®°å½"> |
| | | <template #actions="{ row }"> |
| | | <Button type="link" size="small" @click="handlePrintIssue(row)">æå° / å¯¼åº PDF</Button> |
| | | <Button |
| | | v-if="row.status === MesWmProductIssueStatusEnum.PREPARE" |
| | | type="link" |
| | |
| | | contactAddress: notice?.recipientAddress, |
| | | contactName: notice?.recipientName, |
| | | contactTelephone: notice?.recipientTelephone, |
| | | salesOrderCode: notice?.salesOrderCode, |
| | | salesOrderCode: notice?.saleOrderCode, |
| | | }); |
| | | }, |
| | | status: MesWmSalesNoticeStatusEnum.APPROVED, |
| | |
| | | }, |
| | | { |
| | | field: 'oqcCheckFlag', |
| | | title: 'æ¯å¦æ ¡éª', |
| | | width: 100, |
| | | title: 'éè¦åºè´§æ£éª', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'qualityStatus', |
| | | title: 'è´¨éç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | |
| | | }, |
| | | { |
| | | fieldName: 'oqcCheckFlag', |
| | | label: 'æ¯å¦æ ¡éª', |
| | | label: 'éè¦åºè´§æ£éª', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | |
| | | <div class="flex flex-auto items-center gap-2"> |
| | | <Popconfirm |
| | | v-if="canSubmit" |
| | | title="确认æäº¤è¯¥éå®åºåºåï¼ãæäº¤åå°ä¸è½ä¿®æ¹ã" |
| | | title="确认æäº¤è¯¥éå®åºåºåï¼ãæäº¤åå°ä¸è½ä¿®æ¹ï¼éè¦åºè´§æ£éªçæç»å°è¿å
¥å¾
æ£æµï¼éè´¨æ£åå建并å
³èåºè´§æ£éªåã" |
| | | @confirm="handleSubmit" |
| | | > |
| | | <Button type="primary">æäº¤</Button> |