| | |
| | | VITE_BASE=/ |
| | | |
| | | # 请æ±è·¯å¾ |
| | | VITE_BASE_URL=http://36.213.180.217:9501 |
| | | VITE_BASE_URL=http://47.97.181.254:9001 |
| | | # æ¥å£å°å |
| | | VITE_GLOB_API_URL=http://36.213.180.217:9501/admin-api |
| | | VITE_GLOB_API_URL=http://47.97.181.254:9001/admin-api |
| | | # æä»¶ä¸ä¼ ç±»åï¼server - å端ä¸ä¼ ï¼ client - å端ç´è¿ä¸ä¼ ï¼ä»
æ¯æS3æå¡ |
| | | VITE_UPLOAD_TYPE=server |
| | | |
| | |
| | | */ |
| | | export const companies: Record<string, CompanyConfig> = { |
| | | TEST: { |
| | | name: 'æµè¯å
¬å¸', |
| | | name: 'æ°ç塿²³ç§ä¸', |
| | | logo: '/logos/TEST.png', |
| | | favicon: '/logos/TEST.ico', |
| | | apiUrl: 'http://36.213.180.217:9501/admin-api', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesBagCodeHomeApi { |
| | | /** åºç¡æ°æ®ä¸ä¸è¢ä¸ç ç¶æåå¸é¡¹ */ |
| | | export interface StatusItem { |
| | | status: number; // ç¶æå¼ |
| | | count: number; // æ°é |
| | | } |
| | | |
| | | /** åºç¡æ°æ®ä¸ä¸è¢ä¸ç é¦é¡µæ±æ»ç»è®¡ */ |
| | | export interface Summary { |
| | | mdmItemCount: number; // åç§æ»æ°ï¼mdm_itemï¼ |
| | | mesItemCount: number; // MES åç§æ°ï¼mes_md_itemï¼ |
| | | itemTypeCount: number; // åç§åç±»æ° |
| | | unitMeasureCount: number; // 计éå使° |
| | | lineCount: number; // äº§çº¿æ° |
| | | batchCount: number; // æ¹æ¬¡æ»æ° |
| | | bagCodeCount: number; // è¢ç æ»æ° |
| | | palletCount: number; // æçç æ»æ° |
| | | batchStatusList: StatusItem[]; // æ¹æ¬¡ç¶æåå¸ |
| | | bagCodeStatusList: StatusItem[]; // è¢ç ç¶æåå¸ |
| | | } |
| | | |
| | | /** åºç¡æ°æ®ä¸ä¸è¢ä¸ç è¢ç çæè¶å¿ */ |
| | | export interface Trend { |
| | | date: string; // æ¥æ |
| | | count: number; // è¢ç çææ°é |
| | | } |
| | | } |
| | | |
| | | /** è·å¾åºç¡æ°æ®ä¸ä¸è¢ä¸ç é¦é¡µæ±æ»ç»è®¡ */ |
| | | export function getBagCodeHomeSummary() { |
| | | return requestClient.get<MesBagCodeHomeApi.Summary>( |
| | | '/mes/bag-code-home-statistics/summary', |
| | | ); |
| | | } |
| | | |
| | | /** è·å¾è¢ç çæè¶å¿ */ |
| | | export function getBagCodeHomeTrend(days?: number) { |
| | | return requestClient.get<MesBagCodeHomeApi.Trend[]>( |
| | | '/mes/bag-code-home-statistics/trend', |
| | | { params: { days } }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesProBagCodeApi { |
| | | /** è¢ç ï¼ä¸è¢ä¸ç ï¼ */ |
| | | export interface BagCode { |
| | | id?: number; // ç¼å· |
| | | uuid?: string; // çæç» UUID |
| | | code?: string; // è¢ç ï¼äºç»´ç å
å®¹ï¼ |
| | | bagNo?: number; // æ¹æ¬¡å
è¢åºå· |
| | | batchId?: number; // çäº§æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | lineCode?: string; // 产线ç¼ç |
| | | lineName?: string; // 产线åç§° |
| | | palletId?: number; // ç»å®æçç¼å· |
| | | palletCode?: string; // æçç |
| | | status?: number; // ç¶æï¼åå
¸ mes_pro_bag_code_statusï¼ |
| | | printTime?: string; // 导åºï¼äº¤ä»å°å·ï¼æ¶é´ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | | |
| | | /** è¢ç å页æ¥è¯¢åæ° */ |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | batchId?: number; |
| | | batchCode?: string; |
| | | uuid?: string; |
| | | palletId?: number; |
| | | status?: number; |
| | | createTime?: string[]; |
| | | } |
| | | |
| | | /** çææ°æ®å
è¯·æ± */ |
| | | export interface GenerateParams { |
| | | batchId: number; // çäº§æ¹æ¬¡ç¼å· |
| | | count: number; // çæè¢æ° |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | |
| | | /** ç»å®/è§£ç»æçè¯·æ± */ |
| | | export interface BindParams { |
| | | palletId: number; // æçç¼å· |
| | | ids: number[]; // è¢ç ç¼å·å表 |
| | | } |
| | | |
| | | /** æ«ç è¿½æº¯ä¿¡æ¯ */ |
| | | export interface BagCodeTrace { |
| | | id?: number; // è¢ç ç¼å· |
| | | code?: string; // è¢ç ï¼äºç»´ç å
å®¹ï¼ |
| | | bagNo?: number; // è¢åºå· |
| | | status?: number; // è¢ç ç¶æ |
| | | printTime?: string; // 导åºå°å·æ¶é´ |
| | | batchId?: number; // æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | batchStatus?: number; // æ¹æ¬¡ç¶æ |
| | | produceDate?: string; // çäº§æ¥æ |
| | | lineCode?: string; // 产线ç¼ç |
| | | lineName?: string; // 产线åç§° |
| | | ownerUserId?: number; // 责任人ç¼å· |
| | | ownerUserName?: string; // 责任人æµç§° |
| | | palletId?: number; // æçç¼å· |
| | | palletCode?: string; // æçç |
| | | itemId?: number; // åç§ï¼ç©æï¼ç¼å· |
| | | itemCode?: string; // åç§ç¼ç |
| | | itemName?: string; // åç§åç§° |
| | | itemSpecification?: string; // åç§è§æ ¼ |
| | | } |
| | | } |
| | | |
| | | /** çæè¢ç æ°æ®å
ï¼è¿åçæç» UUIDï¼ */ |
| | | export function generateBagCodes(data: MesProBagCodeApi.GenerateParams) { |
| | | return requestClient.post<string>('/mes/pro/bag-code/generate', data); |
| | | } |
| | | |
| | | /** å é¤è¢ç çæç» */ |
| | | export function deleteBagCodesByUuid(uuid: string) { |
| | | return requestClient.delete(`/mes/pro/bag-code/delete-by-uuid?uuid=${uuid}`); |
| | | } |
| | | |
| | | /** è¢ç ç»å®æç */ |
| | | export function bindBagCodePallet(data: MesProBagCodeApi.BindParams) { |
| | | return requestClient.put('/mes/pro/bag-code/bind-pallet', data); |
| | | } |
| | | |
| | | /** è¢ç è§£ç»æç */ |
| | | export function unbindBagCodePallet(data: MesProBagCodeApi.BindParams) { |
| | | return requestClient.put('/mes/pro/bag-code/unbind-pallet', data); |
| | | } |
| | | |
| | | /** æ¥è¯¢è¢ç 详æ
*/ |
| | | export function getBagCode(id: number) { |
| | | return requestClient.get<MesProBagCodeApi.BagCode>( |
| | | `/mes/pro/bag-code/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ«ç è¿½æº¯ï¼æ ¹æ®è¢ç åæ¥æ¹æ¬¡ã产线ã责任人çä¿¡æ¯ */ |
| | | export function getBagCodeByCode(code: string) { |
| | | return requestClient.get<MesProBagCodeApi.BagCodeTrace>( |
| | | `/mes/pro/bag-code/get-by-code?code=${encodeURIComponent(code)}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢è¢ç å页 */ |
| | | export function getBagCodePage(params: MesProBagCodeApi.PageParams) { |
| | | return requestClient.get<PageResult<MesProBagCodeApi.BagCode>>( |
| | | '/mes/pro/bag-code/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢çæç»çè¢ç å表 */ |
| | | export function getBagCodeListByUuid(uuid: string) { |
| | | return requestClient.get<MesProBagCodeApi.BagCode[]>( |
| | | `/mes/pro/bag-code/list-by-uuid?uuid=${uuid}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹é导åºè¢ç Excelï¼äº¤ä»å°å·ï¼ */ |
| | | export function exportBagCodeExcel(params: MesProBagCodeApi.PageParams) { |
| | | return requestClient.download('/mes/pro/bag-code/export-excel', { params }); |
| | | } |
| | | |
| | | /** æ¹é导åºè¢ç äºç»´ç å¾ç ZIPï¼uuid ä¼å
ï¼å¦åæ batchIdï¼ */ |
| | | export function exportBagQr(params: { uuid?: string; batchId?: number }) { |
| | | return requestClient.download('/mes/pro/bag-code/export-qr', { params }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | import type { MesProBagCodeApi } from '#/api/mes/pro/bagCode'; |
| | | import type { MesProPalletApi } from '#/api/mes/pro/pallet'; |
| | | |
| | | export namespace MesProBatchApi { |
| | | /** çäº§æ¹æ¬¡äººåé
ç½® */ |
| | | export interface BatchWorker { |
| | | id?: number; // ç¼å· |
| | | batchId?: number; // æ¹æ¬¡ç¼å· |
| | | userId?: number; // ç产人åç¨æ·ç¼å· |
| | | userName?: string; // çäº§äººåæµç§° |
| | | workerType?: number; // å²ä½ç±»åï¼åå
¸ mes_pro_batch_worker_typeï¼ |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | |
| | | /** çäº§æ¹æ¬¡ */ |
| | | export interface Batch { |
| | | id?: number; // ç¼å· |
| | | code?: string; // æ¹æ¬¡å·ï¼èªå¨çæï¼ |
| | | itemId?: number; // åç§ï¼äº§åç©æï¼ç¼å· |
| | | itemCode?: string; // åç§ç¼ç ï¼åç«¯æ¼æ¥ï¼ |
| | | itemName?: string; // åç§åç§°ï¼åç«¯æ¼æ¥ï¼ |
| | | itemSpecification?: string; // åç§è§æ ¼ï¼åç«¯æ¼æ¥ï¼ |
| | | lineId?: number; // 产线ç¼å· |
| | | lineCode?: string; // 产线ç¼ç ï¼å端åå¡«ï¼ |
| | | lineName?: string; // 产线åç§°ï¼å端åå¡«ï¼ |
| | | produceDate?: string; // çäº§æ¥æ |
| | | planQuantity?: number; // è®¡åæ°é |
| | | ownerUserId?: number; // è´£ä»»äººç¨æ·ç¼å· |
| | | ownerUserName?: string; // 责任人æµç§° |
| | | bagQuantity?: number; // å·²çæè¢ç æ°é |
| | | palletQuantity?: number; // å·²çææçç æ°é |
| | | status?: number; // æ¹æ¬¡ç¶æï¼åå
¸ mes_pro_batch_statusï¼ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: string; // å建æ¶é´ |
| | | workers?: BatchWorker[]; // ç产人åé
ç½®å表 |
| | | } |
| | | |
| | | /** çäº§æ¹æ¬¡æ«ç 追溯 */ |
| | | export interface BatchTrace { |
| | | id?: number; // çäº§æ¹æ¬¡ç¼å· |
| | | code?: string; // æ¹æ¬¡å·ï¼äºç»´ç å
å®¹ï¼ |
| | | status?: number; // æ¹æ¬¡ç¶æï¼åå
¸ mes_pro_batch_statusï¼ |
| | | produceDate?: string; // çäº§æ¥æ |
| | | lineCode?: string; // 产线ç¼ç |
| | | lineName?: string; // 产线åç§° |
| | | ownerUserId?: number; // è´£ä»»äººç¨æ·ç¼å· |
| | | ownerUserName?: string; // 责任人æµç§° |
| | | planQuantity?: number; // è®¡åæ°é |
| | | bagQuantity?: number; // å·²çæè¢ç æ°é |
| | | palletQuantity?: number; // å·²çææçç æ°é |
| | | remark?: string; // 夿³¨ |
| | | itemId?: number; // åç§ï¼ç©æï¼ç¼å· |
| | | itemCode?: string; // åç§ç¼ç |
| | | itemName?: string; // åç§åç§° |
| | | itemSpecification?: string; // åç§è§æ ¼ |
| | | workers?: BatchWorker[]; // ç产人åé
ç½®å表 |
| | | pallets?: MesProPalletApi.Pallet[]; // æçç å表 |
| | | bags?: MesProBagCodeApi.BagCode[]; // è¢ç åè¡¨ï¼æå¤å 2000 æ¡ï¼æè¢åºå·ååºï¼ |
| | | bagTotal?: number; // è¢ç æ»æ° |
| | | } |
| | | |
| | | /** çäº§æ¹æ¬¡å页æ¥è¯¢åæ° */ |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | itemId?: number; |
| | | lineId?: number; |
| | | lineCode?: string; |
| | | lineName?: string; |
| | | ownerUserId?: number; |
| | | status?: number; |
| | | produceDate?: string[]; |
| | | createTime?: string[]; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢çäº§æ¹æ¬¡å页 */ |
| | | export function getBatchPage(params: MesProBatchApi.PageParams) { |
| | | return requestClient.get<PageResult<MesProBatchApi.Batch>>( |
| | | '/mes/pro/batch/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢çäº§æ¹æ¬¡è¯¦æ
ï¼å«äººåé
ç½®ï¼ */ |
| | | export function getBatch(id: number) { |
| | | return requestClient.get<MesProBatchApi.Batch>( |
| | | `/mes/pro/batch/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢çäº§æ¹æ¬¡ï¼èªå¨çææ¹æ¬¡å·ï¼ */ |
| | | export function createBatch(data: MesProBatchApi.Batch) { |
| | | return requestClient.post<number>('/mes/pro/batch/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹çäº§æ¹æ¬¡ */ |
| | | export function updateBatch(data: MesProBatchApi.Batch) { |
| | | return requestClient.put('/mes/pro/batch/update', data); |
| | | } |
| | | |
| | | /** å é¤çäº§æ¹æ¬¡ */ |
| | | export function deleteBatch(id: number) { |
| | | return requestClient.delete(`/mes/pro/batch/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æ´æ°çäº§æ¹æ¬¡ç¶æ */ |
| | | export function updateBatchStatus(id: number, status: number) { |
| | | return requestClient.put( |
| | | `/mes/pro/batch/update-status?id=${id}&status=${status}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢çäº§æ¹æ¬¡ç²¾ç®å表ï¼ä¾ä¸æéæ©ï¼ */ |
| | | export function getBatchSimpleList() { |
| | | return requestClient.get<MesProBatchApi.Batch[]>('/mes/pro/batch/simple-list'); |
| | | } |
| | | |
| | | /** 导åºçäº§æ¹æ¬¡ Excel */ |
| | | export function exportBatchExcel(params: MesProBatchApi.PageParams) { |
| | | return requestClient.download('/mes/pro/batch/export-excel', { params }); |
| | | } |
| | | |
| | | /** æ«ç è¿½æº¯ï¼æ ¹æ®æ¹æ¬¡å·ï¼äºç»´ç å
容ï¼åæ¥æ¹æ¬¡ã人åãæçãè¢ç ä¿¡æ¯ */ |
| | | export function getBatchByCode(code: string) { |
| | | return requestClient.get<MesProBatchApi.BatchTrace>( |
| | | `/mes/pro/batch/get-by-code?code=${encodeURIComponent(code)}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹éå¯¼åºæ¹æ¬¡äºç»´ç å¾ç ZIPï¼æ¹æ¬¡å¾ + è¯¥æ¹æ¬¡å
¨é¨æçå¾ï¼ */ |
| | | export function exportBatchQr(batchId: number) { |
| | | return requestClient.download(`/mes/pro/batch/export-qr?batchId=${batchId}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesProLineApi { |
| | | /** MES 产线 */ |
| | | export interface Line { |
| | | id?: number; // ç¼å· |
| | | code?: string; // 产线ç¼ç |
| | | name?: string; // 产线åç§° |
| | | status?: number; // ç¶æï¼åå
¸ common_statusï¼ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | | |
| | | /** 产线å页æ¥è¯¢åæ° */ |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | name?: string; |
| | | status?: number; |
| | | createTime?: string[]; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢äº§çº¿å页 */ |
| | | export function getLinePage(params: MesProLineApi.PageParams) { |
| | | return requestClient.get<PageResult<MesProLineApi.Line>>( |
| | | '/mes/pro/line/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢äº§çº¿ç²¾ç®å表ï¼ä¾ä¸æéæ©ï¼ä»
å¯ç¨çäº§çº¿ï¼ */ |
| | | export function getLineSimpleList() { |
| | | return requestClient.get<MesProLineApi.Line[]>('/mes/pro/line/simple-list'); |
| | | } |
| | | |
| | | /** æ¥è¯¢äº§çº¿è¯¦æ
*/ |
| | | export function getLine(id: number) { |
| | | return requestClient.get<MesProLineApi.Line>(`/mes/pro/line/get?id=${id}`); |
| | | } |
| | | |
| | | /** æ°å¢äº§çº¿ */ |
| | | export function createLine(data: MesProLineApi.Line) { |
| | | return requestClient.post('/mes/pro/line/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹äº§çº¿ */ |
| | | export function updateLine(data: MesProLineApi.Line) { |
| | | return requestClient.put('/mes/pro/line/update', data); |
| | | } |
| | | |
| | | /** å é¤äº§çº¿ */ |
| | | export function deleteLine(id: number) { |
| | | return requestClient.delete(`/mes/pro/line/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºäº§çº¿ Excel */ |
| | | export function exportLineExcel(params: MesProLineApi.PageParams) { |
| | | return requestClient.download('/mes/pro/line/export-excel', { params }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | import type { MesProBagCodeApi } from '#/api/mes/pro/bagCode'; |
| | | |
| | | export namespace MesProPalletApi { |
| | | /** æçç */ |
| | | export interface Pallet { |
| | | id?: number; // ç¼å· |
| | | code?: string; // æçç |
| | | batchId?: number; // çäº§æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | palletNo?: number; // æ¹æ¬¡å
æçåºå· |
| | | capacity?: number; // å¯è£
è¢æ°ï¼è¢/æçï¼ |
| | | bagCount?: number; // å·²ç»å®è¢ç æ°é |
| | | status?: number; // ç¶æï¼åå
¸ mes_pro_pallet_statusï¼ |
| | | remark?: string; // 夿³¨ |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | | |
| | | /** æçç å页æ¥è¯¢åæ° */ |
| | | export interface PageParams extends PageParam { |
| | | code?: string; |
| | | batchId?: number; |
| | | batchCode?: string; |
| | | status?: number; |
| | | createTime?: string[]; |
| | | } |
| | | |
| | | /** æçç æ«ç 追溯 */ |
| | | export interface PalletTrace { |
| | | id?: number; // ç¼å· |
| | | code?: string; // æçç ï¼äºç»´ç å
å®¹ï¼ |
| | | palletNo?: number; // æ¹æ¬¡å
æçåºå· |
| | | capacity?: number; // å¯è£
è¢æ° |
| | | bagCount?: number; // å·²ç»å®è¢ç æ°é |
| | | status?: number; // ç¶æï¼åå
¸ mes_pro_pallet_statusï¼ |
| | | batchId?: number; // çäº§æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | | remark?: string; // 夿³¨ |
| | | batchStatus?: number; // æ¹æ¬¡ç¶æ |
| | | produceDate?: string; // çäº§æ¥æ |
| | | lineCode?: string; // 产线ç¼ç |
| | | lineName?: string; // 产线åç§° |
| | | ownerUserId?: number; // 责任人ç¼å· |
| | | ownerUserName?: string; // 责任人æµç§° |
| | | itemId?: number; // åç§ï¼ç©æï¼ç¼å· |
| | | itemCode?: string; // åç§ç¼ç |
| | | itemName?: string; // åç§åç§° |
| | | itemSpecification?: string; // åç§è§æ ¼ |
| | | bags?: MesProBagCodeApi.BagCode[]; // 该æçç»å®çè¢ç å表 |
| | | } |
| | | |
| | | /** æ¹éçæè¯·æ± */ |
| | | export interface BatchCreateParams { |
| | | batchId: number; // çäº§æ¹æ¬¡ç¼å· |
| | | count: number; // çææ°é |
| | | capacity?: number; // å¯è£
è¢æ° |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | } |
| | | |
| | | /** æ¹éçææçç ï¼è¿åçæçæçç¼å·åè¡¨ï¼ */ |
| | | export function batchCreatePallet(data: MesProPalletApi.BatchCreateParams) { |
| | | return requestClient.post<number[]>('/mes/pro/pallet/batch-create', data); |
| | | } |
| | | |
| | | /** å 餿çç */ |
| | | export function deletePallet(id: number) { |
| | | return requestClient.delete(`/mes/pro/pallet/delete?id=${id}`); |
| | | } |
| | | |
| | | /** æ¥è¯¢æçç 详æ
*/ |
| | | export function getPallet(id: number) { |
| | | return requestClient.get<MesProPalletApi.Pallet>( |
| | | `/mes/pro/pallet/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æçç å页 */ |
| | | export function getPalletPage(params: MesProPalletApi.PageParams) { |
| | | return requestClient.get<PageResult<MesProPalletApi.Pallet>>( |
| | | '/mes/pro/pallet/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢æ¹æ¬¡çæçç å表ï¼ä¾ä¸æéæ©ï¼ */ |
| | | export function getPalletSimpleList(batchId: number) { |
| | | return requestClient.get<MesProPalletApi.Pallet[]>( |
| | | `/mes/pro/pallet/simple-list?batchId=${batchId}`, |
| | | ); |
| | | } |
| | | |
| | | /** å¯¼åºæçç Excel */ |
| | | export function exportPalletExcel(params: MesProPalletApi.PageParams) { |
| | | return requestClient.download('/mes/pro/pallet/export-excel', { params }); |
| | | } |
| | | |
| | | /** æ«ç è¿½æº¯ï¼æ ¹æ®æçç ï¼äºç»´ç å
容ï¼åæ¥æ¹æ¬¡ãåç§ã责任人åè¢ç å表 */ |
| | | export function getPalletByCode(code: string) { |
| | | return requestClient.get<MesProPalletApi.PalletTrace>( |
| | | `/mes/pro/pallet/get-by-code?code=${encodeURIComponent(code)}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¹éå¯¼åºææ¹æ¬¡çæçç äºç»´ç å¾ç ZIP */ |
| | | export function exportPalletQr(batchId: number) { |
| | | return requestClient.download(`/mes/pro/pallet/export-qr?batchId=${batchId}`); |
| | | } |
| | |
| | | Button, |
| | | Dropdown, |
| | | Menu, |
| | | Modal, |
| | | Popconfirm, |
| | | Space, |
| | | Tooltip, |
| | |
| | | /** å¤çèåç¹å» */ |
| | | function handleMenuClick(e: any) { |
| | | const action = getDropdownList.value[e.key]; |
| | | if (action && action.onClick && isFunction(action.onClick)) { |
| | | if (!action) { |
| | | return; |
| | | } |
| | | const popConfirm = action.popConfirm; |
| | | if (popConfirm) { |
| | | // 䏿èå项ä¼è¢« Dropdown èªå¨å
³é鿝ï¼åµå¥ Popconfirm æ æ³å¼¹åºï¼ |
| | | // æ
æ¹ç¨å½ä»¤å¼ Modal.confirm å®æç¡®è®¤ |
| | | Modal.confirm({ |
| | | title: popConfirm.title, |
| | | okText: popConfirm.okText, |
| | | cancelText: popConfirm.cancelText, |
| | | onOk: popConfirm.confirm ? () => popConfirm.confirm!() : undefined, |
| | | onCancel: popConfirm.cancel ? () => popConfirm.cancel!() : undefined, |
| | | }); |
| | | return; |
| | | } |
| | | if (action.onClick && isFunction(action.onClick)) { |
| | | action.onClick(); |
| | | } |
| | | } |
| | |
| | | v-for="(action, index) in getDropdownList" |
| | | :key="index" |
| | | :disabled="action.disabled" |
| | | @click="!action.popConfirm && handleMenuClick({ key: index })" |
| | | @click="handleMenuClick({ key: index })" |
| | | > |
| | | <template v-if="action.popConfirm"> |
| | | <Popconfirm v-bind="getPopConfirmProps(action.popConfirm)"> |
| | | <template v-if="action.popConfirm.icon" #icon> |
| | | <IconifyIcon :icon="action.popConfirm.icon" /> |
| | | </template> |
| | | <div |
| | | :class=" |
| | | action.disabled === true |
| | | ? 'cursor-not-allowed text-gray-300' |
| | | : '' |
| | | " |
| | | > |
| | | <IconifyIcon v-if="action.icon" :icon="action.icon" /> |
| | | <span :class="action.icon ? 'ml-1' : ''"> |
| | | {{ action.label }} |
| | | </span> |
| | | </div> |
| | | </Popconfirm> |
| | | </template> |
| | | <template v-else> |
| | | <div |
| | | :class=" |
| | | action.disabled === true |
| | | ? 'cursor-not-allowed text-gray-300' |
| | | : '' |
| | | " |
| | | > |
| | | <IconifyIcon v-if="action.icon" :icon="action.icon" /> |
| | | <div |
| | | :class=" |
| | | action.disabled === true |
| | | ? 'cursor-not-allowed text-gray-300' |
| | | : '' |
| | | " |
| | | > |
| | | <IconifyIcon v-if="action.icon" :icon="action.icon" /> |
| | | <span :class="action.icon ? 'ml-1' : ''"> |
| | | {{ action.label }} |
| | | </div> |
| | | </template> |
| | | </span> |
| | | </div> |
| | | </Menu.Item> |
| | | </Menu> |
| | | </template> |
| | |
| | | copyright: { companyName: __APP_NAME__ }, |
| | | }); |
| | | } |
| | | // 强å¶è¦çé»è®¤é¦é¡µä¸º"åºç¡æ°æ®ä¸ä¸è¢ä¸ç "ï¼ç»è¿ localStorage ç¼å |
| | | preferencesManager.updatePreferences({ |
| | | app: { defaultHomePath: '/bagcode-home' }, |
| | | }); |
| | | if (__APP_FAVICON__) { |
| | | const link = document.querySelector('link[rel="icon"]'); |
| | | if (link) { |
| | |
| | | contentPaddingTop: 0, |
| | | defaultAvatar: |
| | | 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', |
| | | defaultHomePath: '/analytics', |
| | | defaultHomePath: '/bagcode-home', |
| | | dynamicTitle: true, |
| | | enableCheckUpdates: true, |
| | | enableCopyPreferences: true, |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="relative flex min-h-full w-full"> |
| | | <div class="relative flex h-full w-full"> |
| | | <LayoutSidebar |
| | | v-if="sidebarEnableState" |
| | | v-model:draggable="sidebarDraggable" |
| | |
| | | CANCELLED: MesOrderStatusConstants.CANCELLED, |
| | | } as const; |
| | | |
| | | /** MES çäº§æ¹æ¬¡ç¶ææä¸¾ï¼å¯¹é½å端 MesProBatchStatusEnumï¼ */ |
| | | export const MesProBatchStatusEnum = { |
| | | DRAFT: 10, // è稿 |
| | | PRODUCING: 20, // çäº§ä¸ |
| | | FINISHED: 30, // 已宿 |
| | | CANCELED: 40, // å·²ä½åº |
| | | } as const; |
| | | |
| | | /** MES çäº§æ¹æ¬¡äººåå²ä½ç±»åæä¸¾ï¼å¯¹é½å端 MesProBatchWorkerTypeEnumï¼ */ |
| | | export const MesProBatchWorkerTypeEnum = { |
| | | LEADER: 10, // ç»é¿ |
| | | OPERATOR: 20, // æä½å·¥ |
| | | PACKER: 30, // å
è£
å·¥ |
| | | INSPECTOR: 40, // è´¨æ£å |
| | | OTHER: 50, // å
¶ä» |
| | | } as const; |
| | | |
| | | /** MES è¢ç ç¶ææä¸¾ï¼å¯¹é½å端 MesProBagCodeStatusEnumï¼ */ |
| | | export const MesProBagCodeStatusEnum = { |
| | | GENERATED: 10, // å·²çæ |
| | | PRINTED: 20, // 已导åºå°å· |
| | | CANCELED: 30, // å·²ä½åº |
| | | } as const; |
| | | |
| | | /** MES æçç ç¶ææä¸¾ï¼å¯¹é½å端 MesProPalletStatusEnumï¼ */ |
| | | export const MesProPalletStatusEnum = { |
| | | IN_USE: 10, // å¯ç¨ |
| | | CANCELED: 20, // ä½åº |
| | | } as const; |
| | | |
| | | /** MES ç产æ¥å·¥ç¶ææä¸¾ */ |
| | | export const MesProFeedbackStatusEnum = { |
| | | PREPARE: MesOrderStatusConstants.DRAFT, |
| | |
| | | MES_PRO_WORK_ORDER_TYPE: 'mes_pro_work_order_type', // MES å·¥åç±»å |
| | | MES_PRO_WORK_ORDER_SOURCE_TYPE: 'mes_pro_work_order_source_type', // MES 工忥æºç±»å |
| | | MES_PRO_TASK_STATUS: 'mes_pro_task_status', // MES ç产任å¡ç¶æ |
| | | MES_PRO_BATCH_STATUS: 'mes_pro_batch_status', // MES çäº§æ¹æ¬¡ç¶æ |
| | | MES_PRO_BATCH_WORKER_TYPE: 'mes_pro_batch_worker_type', // MES çäº§æ¹æ¬¡äººåå²ä½ç±»å |
| | | MES_PRO_BAG_CODE_STATUS: 'mes_pro_bag_code_status', // MES è¢ç ç¶æ |
| | | MES_PRO_PALLET_STATUS: 'mes_pro_pallet_status', // MES æçç ç¶æ |
| | | MES_PRO_FEEDBACK_STATUS: 'mes_pro_feedback_status', // MES ç产æ¥å·¥ç¶æ |
| | | MES_PRO_FEEDBACK_TYPE: 'mes_pro_feedback_type', // MES ç产æ¥å·¥ç±»å |
| | | MES_PD_PROJECT_STATUS: 'mes_pd_project_status', // MES 产å设计项ç®ç¶æ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { RouteRecordRaw } from 'vue-router'; |
| | | |
| | | const routes: RouteRecordRaw[] = [ |
| | | { |
| | | name: 'BagCodeHome', |
| | | path: '/bagcode-home', |
| | | component: () => import('#/views/dashboard/bagCodeHome/index.vue'), |
| | | meta: { |
| | | icon: 'lucide:scan-barcode', |
| | | order: 1, |
| | | title: 'é¦é¡µ', |
| | | }, |
| | | }, |
| | | ]; |
| | | |
| | | export default routes; |
| | |
| | | import type { RouteRecordRaw } from 'vue-router'; |
| | | |
| | | const routes: RouteRecordRaw[] = [ |
| | | { |
| | | name: 'Analytics', |
| | | path: '/analytics', |
| | | component: () => import('#/views/dashboard/analytics/index.vue'), |
| | | meta: { |
| | | affixTab: true, |
| | | icon: 'lucide:area-chart', |
| | | order: -1, |
| | | title: 'é¦é¡µ', |
| | | }, |
| | | }, |
| | | ]; |
| | | // èé¦é¡µ /analytics å·²éèï¼ç»å½åé»è®¤è·³è½¬ /bagcode-home |
| | | const routes: RouteRecordRaw[] = []; |
| | | |
| | | export default routes; |
| | |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ç©æç¼ç ', |
| | | label: 'åç§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ç©æç¼ç ', |
| | | placeholder: '请è¾å
¥åç§ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ç©æåç§°', |
| | | label: 'åç§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ç©æåç§°', |
| | | placeholder: '请è¾å
¥åç§åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'categoryId', |
| | | label: 'ç©æåç±»', |
| | | label: 'åç§åç±»', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç©æåç±»', |
| | | placeholder: 'è¯·éæ©åç§åç±»', |
| | | allowClear: true, |
| | | api: async () => { |
| | | const res = await getItemTypeSimpleList(); |
| | |
| | | { type: multiple ? 'checkbox' : 'radio', width: 50 }, |
| | | { |
| | | field: 'code', |
| | | title: 'ç©æç¼ç ', |
| | | title: 'åç§ç¼ç ', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: 'ç©æåç§°', |
| | | title: 'åç§åç§°', |
| | | minWidth: 180, |
| | | align: 'left', |
| | | }, |
| | |
| | | }, |
| | | { |
| | | field: 'categoryId', |
| | | title: 'ç©æåç±»', |
| | | title: 'åç§åç±»', |
| | | minWidth: 120, |
| | | align: 'center', |
| | | slots: { default: 'categoryId' }, |
| | |
| | | function handleConfirm() { |
| | | const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; |
| | | if (rows.length === 0) { |
| | | message.warning(multiple.value ? '请è³å°éæ©ä¸æ¡æ°æ®' : 'è¯·éæ©ä¸æ¡æ°æ®'); |
| | | message.warning(multiple.value ? '请è³å°éæ©ä¸ä¸ªåç§' : 'è¯·éæ©ä¸ä¸ªåç§'); |
| | | return; |
| | | } |
| | | emit('selected', multiple.value ? rows : [rows[0]!]); |
| | |
| | | <template> |
| | | <Modal |
| | | v-model:open="open" |
| | | title="ç©æéæ©" |
| | | title="åç§éæ©" |
| | | width="80%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | | @cancel="closeModal" |
| | | > |
| | | <Grid table-title="ç©æå表"> |
| | | <Grid table-title="åç§å表"> |
| | | <template #categoryId="{ row }"> |
| | | <Tag v-if="getCategoryColor(row.categoryId)" :color="getCategoryColor(row.categoryId)">{{ getCategoryName(row.categoryId) }}</Tag> |
| | | <span v-else>{{ getCategoryName(row.categoryId) || '-' }}</span> |
| | |
| | | allowClear: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©ç©æ', |
| | | placeholder: 'è¯·éæ©åç§', |
| | | label: undefined, |
| | | }, |
| | | ); |
| | |
| | | { |
| | | component: "Input", |
| | | fieldName: "code", |
| | | label: "ç©æç¼ç ", |
| | | label: "åç§ç¼ç ", |
| | | componentProps: { |
| | | maxLength: 64, |
| | | placeholder: "请è¾å
¥ç©æç¼ç ", |
| | | placeholder: "请è¾å
¥åç§ç¼ç ", |
| | | }, |
| | | rules: z.string().min(1, 'ç©æç¼ç ä¸è½ä¸ºç©º').max(64), |
| | | rules: z.string().min(1, 'åç§ç¼ç ä¸è½ä¸ºç©º').max(64), |
| | | suffix: () => |
| | | h( |
| | | Button, |
| | |
| | | { |
| | | component: "Input", |
| | | fieldName: "name", |
| | | label: "ç©æåç§°", |
| | | label: "åç§åç§°", |
| | | componentProps: { |
| | | placeholder: "请è¾å
¥ç©æåç§°", |
| | | placeholder: "请è¾å
¥åç§åç§°", |
| | | }, |
| | | rules: "required", |
| | | }, |
| | | { |
| | | component: "TreeSelect", |
| | | fieldName: "categoryId", |
| | | label: "ç©æåç±»", |
| | | label: "åç§åç±»", |
| | | rules: "selectRequired", |
| | | componentProps: { |
| | | placeholder: "è¯·éæ©ç©æåç±»", |
| | | placeholder: "è¯·éæ©åç§åç±»", |
| | | allowClear: true, |
| | | treeDefaultExpandAll: true, |
| | | treeData: categoryTreeData, |
| | |
| | | return [ |
| | | { |
| | | fieldName: "code", |
| | | label: "ç©æç¼ç ", |
| | | label: "åç§ç¼ç ", |
| | | component: "Input", |
| | | componentProps: { |
| | | placeholder: "请è¾å
¥ç©æç¼ç ", |
| | | placeholder: "请è¾å
¥åç§ç¼ç ", |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: "name", |
| | | label: "ç©æåç§°", |
| | | label: "åç§åç§°", |
| | | component: "Input", |
| | | componentProps: { |
| | | placeholder: "请è¾å
¥ç©æåç§°", |
| | | placeholder: "请è¾å
¥åç§åç§°", |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | |
| | | { type: "checkbox", width: 40 }, |
| | | { |
| | | field: "code", |
| | | title: "ç©æç¼ç ", |
| | | title: "åç§ç¼ç ", |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: "name", |
| | | title: "ç©æåç§°", |
| | | title: "åç§åç§°", |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | |
| | | }, |
| | | { |
| | | field: "categoryName", |
| | | title: "ç©æåç±»", |
| | | title: "åç§åç±»", |
| | | minWidth: 120, |
| | | slots: { default: "categoryName" }, |
| | | }, |
| | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | // async function handleExport() { |
| | | // const data = await exportItem(await gridApi.formApi.getValues()); |
| | | // downloadFileFromBlobPart({ fileName: 'ç©æ.xls', source: data }); |
| | | // downloadFileFromBlobPart({ fileName: 'åç§.xls', source: data }); |
| | | // } |
| | | |
| | | /** åå»ºç©æ */ |
| | | /** å建åç§ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾ç©æ */ |
| | | /** ç¼è¾åç§ */ |
| | | function handleEdit(row: MdmItemApi.Item) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤ç©æ */ |
| | | /** å é¤åç§ */ |
| | | async function handleDelete(row: MdmItemApi.Item) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.name]), |
| | |
| | | } |
| | | } |
| | | |
| | | /** æ¹éå é¤ç©æ */ |
| | | /** æ¹éå é¤åç§ */ |
| | | async function handleDeleteBatch() { |
| | | await confirm($t('ui.actionMessage.deleteBatchConfirm')); |
| | | const hideLoading = message.loading({ |
| | |
| | | |
| | | const syncingId = ref<number | null>(null); |
| | | |
| | | /** åæ¢åæ¥ç¶æï¼å
³éå¼å
³ä¼ä» MES å é¤ç©æï¼é确认 */ |
| | | /** åæ¢åæ¥ç¶æï¼å
³éå¼å
³ä¼ä» MES å é¤åç§ï¼é确认 */ |
| | | async function handleSyncChange(row: MdmItemApi.Item, checked: boolean) { |
| | | if (!checked) { |
| | | await confirm('å
³é忥å°ä» MES å é¤è¯¥ç©æåå
³èé
ç½®ï¼BOM/SOP/SIP/æ¹æ¬¡é
ç½®ï¼ï¼ç¡®è®¤ç»§ç»ï¼'); |
| | | await confirm('å
³é忥å°ä» MES å é¤è¯¥åç§åå
³èé
ç½®ï¼BOM/SOP/SIP/æ¹æ¬¡é
ç½®ï¼ï¼ç¡®è®¤ç»§ç»ï¼'); |
| | | } |
| | | syncingId.value = row.id!; |
| | | try { |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="ç©æå表"> |
| | | <Grid table-title="åç§å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç©æ']), |
| | | label: $t('ui.actionTitle.create', ['åç§']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mdm:item:create'], |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal class="w-[1000px]" :title="formData?.id ? $t('ui.actionTitle.edit', ['ç©æ']) : $t('ui.actionTitle.create', ['ç©æ'])"> |
| | | <Modal class="w-[1000px]" :title="formData?.id ? $t('ui.actionTitle.edit', ['åç§']) : $t('ui.actionTitle.create', ['åç§'])"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { EChartsOption } from '@vben/plugins/echarts'; |
| | | |
| | | /** è¢ç çæè¶å¿æ±ç¶å¾é
ç½® */ |
| | | export function getBagCodeTrendChartOptions( |
| | | dates: string[], |
| | | counts: number[], |
| | | ): EChartsOption { |
| | | return { |
| | | grid: { bottom: 40, left: 50, right: 20, top: 20 }, |
| | | legend: { bottom: 0, data: ['è¢ç çæé'] }, |
| | | series: [ |
| | | { |
| | | barMaxWidth: 32, |
| | | data: counts, |
| | | itemStyle: { borderRadius: 4, color: '#409EFF' }, |
| | | name: 'è¢ç çæé', |
| | | type: 'bar', |
| | | }, |
| | | ], |
| | | tooltip: { axisPointer: { type: 'shadow' }, trigger: 'axis' }, |
| | | xAxis: { axisTick: { show: false }, data: dates, type: 'category' }, |
| | | yAxis: { minInterval: 1, type: 'value' }, |
| | | }; |
| | | } |
| | | |
| | | /** ç¶æåå¸ç¯å½¢å¾é
ç½® */ |
| | | export function getStatusChartOptions( |
| | | data: Array<{ itemStyle: { color: string }; name: string; value: number }>, |
| | | ): EChartsOption { |
| | | return { |
| | | legend: { bottom: 0, type: 'scroll' }, |
| | | series: [ |
| | | { |
| | | avoidLabelOverlap: true, |
| | | data, |
| | | emphasis: { label: { fontSize: 14, fontWeight: 'bold', show: true } }, |
| | | itemStyle: { borderColor: '#fff', borderRadius: 6, borderWidth: 2 }, |
| | | label: { formatter: '{b}\n{c}', show: true }, |
| | | radius: ['40%', '70%'], |
| | | type: 'pie', |
| | | }, |
| | | ], |
| | | tooltip: { formatter: '{b}: {c} ({d}%)', trigger: 'item' }, |
| | | }; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | /** é¦é¡µæ±æ»ç»è®¡é»è®¤å¼ */ |
| | | export const defaultSummary: MesBagCodeHomeApi.Summary = { |
| | | bagCodeCount: 0, |
| | | bagCodeStatusList: [], |
| | | batchCount: 0, |
| | | batchStatusList: [], |
| | | itemTypeCount: 0, |
| | | lineCount: 0, |
| | | mdmItemCount: 0, |
| | | mesItemCount: 0, |
| | | palletCount: 0, |
| | | unitMeasureCount: 0, |
| | | }; |
| | | |
| | | /** æ¹æ¬¡ç¶æå¯¹åºçé¢è²æ å°ï¼ç¶æåå
¸ï¼10 è稿 20 çäº§ä¸ 30 已宿 40 å·²ä½åºï¼ */ |
| | | export const BATCH_STATUS_COLOR_MAP: Record<number, string> = { |
| | | 10: '#909399', // è稿 |
| | | 20: '#409EFF', // çäº§ä¸ |
| | | 30: '#67C23A', // 已宿 |
| | | 40: '#F56C6C', // å·²ä½åº |
| | | }; |
| | | |
| | | /** è¢ç ç¶æå¯¹åºçé¢è²æ å°ï¼ç¶æåå
¸ï¼10 å·²çæ 20 已导åºå°å· 30 å·²ä½åºï¼ */ |
| | | export const BAG_CODE_STATUS_COLOR_MAP: Record<number, string> = { |
| | | 10: '#409EFF', // å·²çæ |
| | | 20: '#E6A23C', // 已导åºå°å· |
| | | 30: '#F56C6C', // å·²ä½åº |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { onMounted, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { Col, Row, message } from 'ant-design-vue'; |
| | | |
| | | import { getBagCodeHomeSummary } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { defaultSummary } from './data'; |
| | | import BagCodeCards from './modules/bag-code-cards.vue'; |
| | | import BagCodeStatusChart from './modules/bag-code-status-chart.vue'; |
| | | import BasicCards from './modules/basic-cards.vue'; |
| | | import BatchStatusChart from './modules/batch-status-chart.vue'; |
| | | import Shortcuts from './modules/shortcuts.vue'; |
| | | import TrendChart from './modules/trend-chart.vue'; |
| | | |
| | | defineOptions({ name: 'DashboardBagCodeHome' }); |
| | | |
| | | const router = useRouter(); |
| | | const summary = ref<MesBagCodeHomeApi.Summary>(defaultSummary); // é¦é¡µæ±æ»ç»è®¡ |
| | | |
| | | /** 跳转å°ç®æ 页é¢ï¼æè·¯ç± nameï¼æªå è½½æ¶æç¤ºï¼ */ |
| | | async function handleNavigate(name: string) { |
| | | try { |
| | | await router.push({ name }); |
| | | } catch { |
| | | message.warning('å½åèåå°æªå è½½ï¼è¯·ä»å·¦ä¾§èåè¿å
¥å¯¹åºé¡µé¢'); |
| | | } |
| | | } |
| | | |
| | | /** å è½½é¦é¡µæ±æ»ç»è®¡ */ |
| | | async function loadSummary() { |
| | | summary.value = await getBagCodeHomeSummary(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadSummary(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page> |
| | | <!-- 第ä¸è¡ï¼åºç¡æ°æ®å¡ç --> |
| | | <BasicCards :summary="summary" class="mb-2" @navigate="handleNavigate" /> |
| | | |
| | | <!-- 第äºè¡ï¼ä¸è¢ä¸ç å¡ç --> |
| | | <BagCodeCards :summary="summary" class="mb-4" @navigate="handleNavigate" /> |
| | | |
| | | <!-- 第ä¸è¡ï¼è¢ç è¶å¿ + æ¹æ¬¡/è¢ç ç¶æåå¸ --> |
| | | <Row :gutter="16" class="mb-4"> |
| | | <Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24" class="mb-4"> |
| | | <TrendChart /> |
| | | </Col> |
| | | <Col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" class="mb-4"> |
| | | <BatchStatusChart :list="summary.batchStatusList" /> |
| | | </Col> |
| | | <Col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" class="mb-4"> |
| | | <BagCodeStatusChart :list="summary.bagCodeStatusList" /> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <!-- 第åè¡ï¼å¿«æ·å
¥å£ --> |
| | | <Shortcuts @navigate="handleNavigate" /> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { CountTo } from '@vben/common-ui'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Card, Col, Row } from 'ant-design-vue'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeBagCodeCards' }); |
| | | |
| | | const props = defineProps<{ |
| | | summary: MesBagCodeHomeApi.Summary; |
| | | }>(); |
| | | |
| | | const emit = defineEmits<{ |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | /** ä¸è¢ä¸ç å¡çé
ç½® */ |
| | | const cards = [ |
| | | { |
| | | gradient: 'linear-gradient(135deg, #0ea5e9, #38bdf8)', |
| | | icon: 'lucide:factory', |
| | | name: '产线', |
| | | routeName: 'MesProLine', |
| | | value: () => props.summary.lineCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #f59e0b, #fbbf24)', |
| | | icon: 'lucide:layers', |
| | | name: 'çäº§æ¹æ¬¡', |
| | | routeName: 'MesProBatch', |
| | | value: () => props.summary.batchCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #ec4899, #f472b6)', |
| | | icon: 'lucide:scan-barcode', |
| | | name: 'è¢ç ', |
| | | routeName: 'MesProBagCode', |
| | | value: () => props.summary.bagCodeCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #14b8a6, #2dd4bf)', |
| | | icon: 'lucide:grid-2x2', |
| | | name: 'æçç ', |
| | | routeName: 'MesProPallet', |
| | | value: () => props.summary.palletCount, |
| | | }, |
| | | ]; |
| | | </script> |
| | | |
| | | <template> |
| | | <Row :gutter="16"> |
| | | <Col v-for="card in cards" :key="card.name" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <Card |
| | | class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg" |
| | | @click="emit('navigate', card.routeName)" |
| | | > |
| | | <div class="flex items-center gap-4"> |
| | | <div |
| | | class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white" |
| | | :style="{ background: card.gradient }" |
| | | > |
| | | <IconifyIcon class="size-7" :icon="card.icon" /> |
| | | </div> |
| | | <div> |
| | | <div class="text-muted-foreground mb-1 text-sm">{{ card.name }}</div> |
| | | <CountTo class="text-2xl font-bold leading-tight" :end-val="card.value()" :duration="1500" /> |
| | | </div> |
| | | </div> |
| | | </Card> |
| | | </Col> |
| | | </Row> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { EchartsUIType } from '@vben/plugins/echarts'; |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictLabel } from '@vben/hooks'; |
| | | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; |
| | | |
| | | import { Card } from 'ant-design-vue'; |
| | | |
| | | import { getStatusChartOptions } from '../chart-options'; |
| | | import { BAG_CODE_STATUS_COLOR_MAP } from '../data'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeBagCodeStatusChart' }); |
| | | |
| | | const props = defineProps<{ |
| | | list: MesBagCodeHomeApi.StatusItem[]; |
| | | }>(); |
| | | |
| | | const chartRef = ref<EchartsUIType>(); |
| | | const { renderEcharts } = useEcharts(chartRef); |
| | | |
| | | watch( |
| | | () => props.list, |
| | | async (list) => { |
| | | const data = list.map((item) => ({ |
| | | itemStyle: { color: BAG_CODE_STATUS_COLOR_MAP[item.status] || '#909399' }, |
| | | name: getDictLabel(DICT_TYPE.MES_PRO_BAG_CODE_STATUS, item.status) || `ç¶æ${item.status}`, |
| | | value: item.count, |
| | | })); |
| | | await renderEcharts(getStatusChartOptions(data)); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Card title="è¢ç ç¶æåå¸" class="h-full"> |
| | | <EchartsUI ref="chartRef" class="h-[320px] w-full" /> |
| | | </Card> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { CountTo } from '@vben/common-ui'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Card, Col, Row } from 'ant-design-vue'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeBasicCards' }); |
| | | |
| | | const props = defineProps<{ |
| | | summary: MesBagCodeHomeApi.Summary; |
| | | }>(); |
| | | |
| | | const emit = defineEmits<{ |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | /** åºç¡æ°æ®å¡çé
ç½® */ |
| | | const cards = [ |
| | | { |
| | | gradient: 'linear-gradient(135deg, #409eff, #66b1ff)', |
| | | icon: 'lucide:package', |
| | | name: 'åç§æ»æ°', |
| | | routeName: 'MdmItem', |
| | | value: () => props.summary.mdmItemCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #67c23a, #85ce61)', |
| | | icon: 'lucide:box', |
| | | name: 'MES åç§', |
| | | routeName: 'MesMdItem', |
| | | value: () => props.summary.mesItemCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #e6a23c, #ebb563)', |
| | | icon: 'lucide:folder-tree', |
| | | name: 'åç§åç±»', |
| | | routeName: 'MesMdItemType', |
| | | value: () => props.summary.itemTypeCount, |
| | | }, |
| | | { |
| | | gradient: 'linear-gradient(135deg, #7c3aed, #9461f5)', |
| | | icon: 'lucide:ruler', |
| | | name: '计éåä½', |
| | | routeName: 'MdmUnit', |
| | | value: () => props.summary.unitMeasureCount, |
| | | }, |
| | | ]; |
| | | </script> |
| | | |
| | | <template> |
| | | <Row :gutter="16"> |
| | | <Col v-for="card in cards" :key="card.name" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <Card |
| | | class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg" |
| | | @click="emit('navigate', card.routeName)" |
| | | > |
| | | <div class="flex items-center gap-4"> |
| | | <div |
| | | class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white" |
| | | :style="{ background: card.gradient }" |
| | | > |
| | | <IconifyIcon class="size-7" :icon="card.icon" /> |
| | | </div> |
| | | <div> |
| | | <div class="text-muted-foreground mb-1 text-sm">{{ card.name }}</div> |
| | | <CountTo class="text-2xl font-bold leading-tight" :end-val="card.value()" :duration="1500" /> |
| | | </div> |
| | | </div> |
| | | </Card> |
| | | </Col> |
| | | </Row> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { EchartsUIType } from '@vben/plugins/echarts'; |
| | | import type { MesBagCodeHomeApi } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { ref, watch } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictLabel } from '@vben/hooks'; |
| | | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; |
| | | |
| | | import { Card } from 'ant-design-vue'; |
| | | |
| | | import { getStatusChartOptions } from '../chart-options'; |
| | | import { BATCH_STATUS_COLOR_MAP } from '../data'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeBatchStatusChart' }); |
| | | |
| | | const props = defineProps<{ |
| | | list: MesBagCodeHomeApi.StatusItem[]; |
| | | }>(); |
| | | |
| | | const chartRef = ref<EchartsUIType>(); |
| | | const { renderEcharts } = useEcharts(chartRef); |
| | | |
| | | watch( |
| | | () => props.list, |
| | | async (list) => { |
| | | const data = list.map((item) => ({ |
| | | itemStyle: { color: BATCH_STATUS_COLOR_MAP[item.status] || '#909399' }, |
| | | name: getDictLabel(DICT_TYPE.MES_PRO_BATCH_STATUS, item.status) || `ç¶æ${item.status}`, |
| | | value: item.count, |
| | | })); |
| | | await renderEcharts(getStatusChartOptions(data)); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | </script> |
| | | |
| | | <template> |
| | | <Card title="æ¹æ¬¡ç¶æåå¸" class="h-full"> |
| | | <EchartsUI ref="chartRef" class="h-[320px] w-full" /> |
| | | </Card> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Card, Col, Row, message } from 'ant-design-vue'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeShortcuts' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | /** å¿«æ·å
¥å£åè¡¨ï¼æä¸å¡åç²¾ç®ä¸º 4 ä¸ªæ ¸å¿å
¥å£ */ |
| | | const shortcuts = [ |
| | | { bgColor: '#409EFF', icon: 'lucide:package', name: 'åç§ç®¡ç', routeName: 'MdmItem' }, |
| | | { bgColor: '#E6A23C', icon: 'lucide:folder-tree', name: 'åç§åç±»', routeName: 'MesMdItemType' }, |
| | | { bgColor: '#f59e0b', icon: 'lucide:layers', name: 'æ¹æ¬¡ç产', routeName: 'MesProBatch' }, |
| | | { bgColor: '#ec4899', icon: 'lucide:scan-barcode', name: 'èµç 管ç', routeName: 'MesProBagCode' }, |
| | | ]; |
| | | |
| | | /** 跳转å°ç®æ 页é¢ï¼æè·¯ç± nameï¼æªå è½½æ¶æç¤ºï¼ */ |
| | | async function handleNavigate(routeName: string) { |
| | | try { |
| | | await router.push({ name: routeName }); |
| | | } catch { |
| | | message.warning('å½åèåå°æªå è½½ï¼è¯·ä»å·¦ä¾§èåè¿å
¥å¯¹åºé¡µé¢'); |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Card title="å¿«æ·å
¥å£" class="h-full"> |
| | | <Row :gutter="16"> |
| | | <Col v-for="item in shortcuts" :key="item.name" :lg="6" :md="8" :sm="12" :xs="12" class="mb-4"> |
| | | <div |
| | | class="hover:bg-accent flex cursor-pointer flex-col items-center gap-2 rounded-lg py-3 transition-all hover:-translate-y-0.5" |
| | | @click="handleNavigate(item.routeName)" |
| | | > |
| | | <div |
| | | class="flex size-12 items-center justify-center rounded-xl text-white" |
| | | :style="{ background: item.bgColor }" |
| | | > |
| | | <IconifyIcon class="size-6" :icon="item.icon" /> |
| | | </div> |
| | | <span class="text-sm">{{ item.name }}</span> |
| | | </div> |
| | | </Col> |
| | | </Row> |
| | | </Card> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { EchartsUIType } from '@vben/plugins/echarts'; |
| | | |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; |
| | | |
| | | import { Card, RadioButton, RadioGroup } from 'ant-design-vue'; |
| | | |
| | | import { getBagCodeHomeTrend } from '#/api/mes/bag-code-home'; |
| | | |
| | | import { getBagCodeTrendChartOptions } from '../chart-options'; |
| | | |
| | | defineOptions({ name: 'BagCodeHomeTrendChart' }); |
| | | |
| | | const trendDays = ref(7); // å½åéä¸ç天æ°èå´ |
| | | const chartRef = ref<EchartsUIType>(); |
| | | const { renderEcharts } = useEcharts(chartRef); |
| | | |
| | | /** å è½½è¢ç çæè¶å¿æ°æ®å¹¶æ¸²æå¾è¡¨ */ |
| | | async function loadData() { |
| | | const data = await getBagCodeHomeTrend(trendDays.value); |
| | | const dates = data.map((d) => d.date.slice(5)); |
| | | const counts = data.map((d) => d.count); |
| | | await renderEcharts(getBagCodeTrendChartOptions(dates, counts)); |
| | | } |
| | | |
| | | /** 忢天æ°èå´ */ |
| | | function handleDaysChange() { |
| | | loadData(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Card title="è¢ç çæè¶å¿" class="h-full"> |
| | | <template #extra> |
| | | <RadioGroup v-model:value="trendDays" size="small" @change="handleDaysChange"> |
| | | <RadioButton :value="7">è¿ 7 天</RadioButton> |
| | | <RadioButton :value="30">è¿ 30 天</RadioButton> |
| | | </RadioGroup> |
| | | </template> |
| | | <EchartsUI ref="chartRef" class="h-[320px] w-full" /> |
| | | </Card> |
| | | </template> |
| | |
| | | |
| | | const open = ref(false); // å¼¹çªæ¯å¦æå¼ |
| | | const multiple = ref(true); // æ¯å¦å¤é |
| | | const selectedRows = ref<MesMdItemApi.Item[]>([]); // å·²éç©æå表 |
| | | const selectedRows = ref<MesMdItemApi.Item[]>([]); // å·²éåç§å表 |
| | | const selectedItemTypeId = ref<number>(); // å½åçéåç±»ç¼å· |
| | | const preSelectedIds = ref<number[]>([]); // é¢éç©æç¼å·å表 |
| | | const productIds = ref<number[]>([]); // éå®å¯éçç©æç¼å·å表ï¼ä¸ºç©ºåä¸éå¶ï¼ |
| | | const typeTreeRef = ref<InstanceType<typeof MdItemTypeTree>>(); // ç©æåç±»æ |
| | | const preSelectedIds = ref<number[]>([]); // é¢éåç§ç¼å·å表 |
| | | const productIds = ref<number[]>([]); // éå®å¯éçåç§ç¼å·å表ï¼ä¸ºç©ºåä¸éå¶ï¼ |
| | | const typeTreeRef = ref<InstanceType<typeof MdItemTypeTree>>(); // åç§åç±»æ |
| | | |
| | | /** è·åå½åè¡¨æ ¼æ°æ® */ |
| | | function getTableRows() { |
| | |
| | | handleConfirm(); |
| | | } |
| | | |
| | | /** æåç±»çéç©æ */ |
| | | /** æåç±»çéåç§ */ |
| | | function handleItemTypeNodeClick(row: MesMdItemTypeApi.ItemType | undefined) { |
| | | selectedItemTypeId.value = row?.id; |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** åæ¾é¢éç©æ */ |
| | | /** åæ¾é¢éåç§ */ |
| | | async function applyPreSelection() { |
| | | if (preSelectedIds.value.length === 0) { |
| | | return; |
| | |
| | | itemTypeId: selectedItemTypeId.value, |
| | | status: CommonStatusEnum.ENABLE, |
| | | }; |
| | | // éå®å¯éç©æèå´ |
| | | // éå®å¯éåç§èå´ |
| | | if (productIds.value.length > 0) { |
| | | params.pageSize = 1000; |
| | | params.ids = productIds.value.join(','); |
| | |
| | | await gridApi.formApi.resetForm(); |
| | | } |
| | | |
| | | /** æå¼ç©æéæ©å¼¹çª */ |
| | | /** æå¼åç§éæ©å¼¹çª */ |
| | | async function openModal( |
| | | selectedIds?: number[], |
| | | options?: { multiple?: boolean; productIds?: number[] } |
| | |
| | | await applyPreSelection(); |
| | | } |
| | | |
| | | /** å
³éç©æéæ©å¼¹çª */ |
| | | /** å
³éåç§éæ©å¼¹çª */ |
| | | async function closeModal() { |
| | | open.value = false; |
| | | await resetQueryState(); |
| | | } |
| | | |
| | | /** ç¡®è®¤éæ©ç©æ */ |
| | | /** ç¡®è®¤éæ©åç§ */ |
| | | function handleConfirm() { |
| | | const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; |
| | | if (rows.length === 0) { |
| | |
| | | |
| | | <template> |
| | | <Modal v-model:open="open" |
| | | title="ç©æäº§åéæ©" |
| | | title="åç§äº§åéæ©" |
| | | width="80%" |
| | | :destroy-on-close="true" |
| | | @ok="handleConfirm" |
| | |
| | | @node-click="handleItemTypeNodeClick" /> |
| | | </Card> |
| | | <div class="w-4/5"> |
| | | <Grid table-title="ç©æäº§åå表" /> |
| | | <Grid table-title="åç§äº§åå表" /> |
| | | </div> |
| | | </div> |
| | | </Modal> |
| | |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | multiple: false, |
| | | placeholder: 'è¯·éæ©äº§åç©æ', |
| | | placeholder: 'è¯·éæ©äº§ååç§', |
| | | productIds: () => [], |
| | | }, |
| | | ); |
| | |
| | | : props.modelValue != null), |
| | | ); |
| | | |
| | | /** æ ¹æ®ç©æç¼å·åæ¾éæ©å¨ */ |
| | | /** æ ¹æ®åç§ç¼å·åæ¾éæ©å¨ */ |
| | | async function resolveItemsById(values: number | number[] | undefined) { |
| | | if (values == null || (Array.isArray(values) && values.length === 0)) { |
| | | selectedItems.value = []; |
| | |
| | | { immediate: true }, |
| | | ); |
| | | |
| | | /** æ¸
空已éç©æ */ |
| | | /** æ¸
空已éåç§ */ |
| | | function clearSelected() { |
| | | selectedItems.value = []; |
| | | if (props.multiple) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** æå¼ç©æéæ©å¼¹çª */ |
| | | /** æå¼åç§éæ©å¼¹çª */ |
| | | function handleClick(event: MouseEvent) { |
| | | if (props.disabled) { |
| | | return; |
| | |
| | | }); |
| | | } |
| | | |
| | | /** åå¡«éä¸çç©æ */ |
| | | /** åå¡«éä¸çåç§ */ |
| | | function handleSelected(rows: MesMdItemApi.Item[]) { |
| | | if (props.multiple) { |
| | | selectedItems.value = rows; |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** ç©æéæ©å¼¹çªæç´¢è¡¨å */ |
| | | /** åç§éæ©å¼¹çªæç´¢è¡¨å */ |
| | | export function useItemSelectGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'ç©æç¼ç ', |
| | | label: 'åç§ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ç©æç¼ç ', |
| | | placeholder: '请è¾å
¥åç§ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: 'ç©æåç§°', |
| | | label: 'åç§åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ç©æåç§°', |
| | | placeholder: '请è¾å
¥åç§åç§°', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç©æéæ©å¼¹çªåè¡¨åæ®µ */ |
| | | /** åç§éæ©å¼¹çªåè¡¨åæ®µ */ |
| | | export function useItemSelectGridColumns( |
| | | multiple = true, |
| | | ): VxeTableGridOptions<MesMdItemApi.Item>['columns'] { |
| | |
| | | { type: multiple ? 'checkbox' : 'radio', width: 50 }, |
| | | { |
| | | field: 'code', |
| | | title: 'ç©æç¼ç ', |
| | | title: 'åç§ç¼ç ', |
| | | width: 180, |
| | | }, |
| | | { |
| | | field: 'name', |
| | | title: 'ç©æåç§°', |
| | | title: 'åç§åç§°', |
| | | minWidth: 160, |
| | | align: 'left', |
| | | }, |
| | |
| | | }, |
| | | { |
| | | field: 'itemOrProduct', |
| | | title: 'ç©æ/产å', |
| | | title: 'åç§/产å', |
| | | width: 110, |
| | | align: 'center', |
| | | cellRender: { |
| | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç©æåç±»ç表å */ |
| | | /** æ°å¢/ä¿®æ¹åç§åç±»ç表å */ |
| | | export function useFormSchema( |
| | | formType: FormType, |
| | | formApi?: VbenFormApi, |
| | |
| | | }, |
| | | { |
| | | fieldName: 'itemOrProduct', |
| | | label: 'ç©æ/äº§åæ è¯', |
| | | label: 'åç§/äº§åæ è¯', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | |
| | | }, |
| | | { |
| | | field: 'itemOrProduct', |
| | | title: 'ç©æ/产å', |
| | | title: 'åç§/产å', |
| | | width: 130, |
| | | align: 'center', |
| | | cellRender: { |
| | |
| | | |
| | | <template> |
| | | <Page auto-content-height><FormModal @success="handleRefresh" /> |
| | | <Grid table-title="ç©æåç±»å表"> |
| | | <Grid table-title="åç§åç±»å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç©æåç±»']), |
| | | label: $t('ui.actionTitle.create', ['åç§åç±»']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:md-item-type:create'], |
| | |
| | | const formData = ref<MesMdItemTypeApi.ItemType>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['ç©æåç±»']) |
| | | : $t('ui.actionTitle.create', ['ç©æåç±»']); |
| | | ? $t('ui.actionTitle.edit', ['åç§åç±»']) |
| | | : $t('ui.actionTitle.create', ['åç§åç±»']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProBagCodeApi } from '#/api/mes/pro/bagCode'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'è¢ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥è¢ç ï¼äºç»´ç å
容ï¼', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'batchCode', |
| | | label: 'æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æ¹æ¬¡å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'uuid', |
| | | label: 'çæç»', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥çæç»ç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'è¢ç ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PRO_BAG_CODE_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©è¢ç ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesProBagCodeApi.BagCode>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { |
| | | field: 'code', |
| | | title: 'è¢ç ï¼äºç»´ç å
容ï¼', |
| | | fixed: 'left', |
| | | minWidth: 220, |
| | | }, |
| | | { |
| | | field: 'bagNo', |
| | | title: 'è¢åºå·', |
| | | width: 80, |
| | | }, |
| | | { |
| | | field: 'uuid', |
| | | title: 'çæç»', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'batchCode', |
| | | title: 'æ¹æ¬¡å·', |
| | | minWidth: 190, |
| | | }, |
| | | { |
| | | field: 'lineCode', |
| | | title: '产线ç¼ç ', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'lineName', |
| | | title: '产线åç§°', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'palletCode', |
| | | title: 'ç»å®æçç ', |
| | | minWidth: 190, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'è¢ç ç¶æ', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_BAG_CODE_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'printTime', |
| | | title: '导åºå°å·æ¶é´', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { MesProBagCodeApi } from "#/api/mes/pro/bagCode"; |
| | | |
| | | import { confirm, Page, useVbenModal } from "@vben/common-ui"; |
| | | import { MesProBagCodeStatusEnum } from "@vben/constants"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { |
| | | deleteBagCodesByUuid, |
| | | exportBagCodeExcel, |
| | | exportBagQr, |
| | | getBagCodePage, |
| | | unbindBagCodePallet, |
| | | } from "#/api/mes/pro/bagCode"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import BindPalletModal from "./modules/bind-pallet.vue"; |
| | | import GenerateModal from "./modules/generate-form.vue"; |
| | | import QrTraceModal from "../components/qr-trace-modal.vue"; |
| | | |
| | | defineOptions({ name: "MesProBagCode" }); |
| | | |
| | | const [GenerateFormModal, generateFormModalApi] = useVbenModal({ |
| | | connectedComponent: GenerateModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [BindPalletFormModal, bindPalletFormModalApi] = useVbenModal({ |
| | | connectedComponent: BindPalletModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [QrTraceQueryModal, qrTraceQueryModalApi] = useVbenModal({ |
| | | connectedComponent: QrTraceModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** çææ°æ®å
*/ |
| | | function handleGenerate() { |
| | | generateFormModalApi.open(); |
| | | } |
| | | |
| | | /** æ«ç 追溯 */ |
| | | function handleTrace() { |
| | | qrTraceQueryModalApi.open(); |
| | | } |
| | | |
| | | /** æ¥çäºç»´ç ï¼è¡å
é¢å¡«ç å¼ï¼ */ |
| | | function handleViewQr(row: MesProBagCodeApi.BagCode) { |
| | | qrTraceQueryModalApi.setData({ code: row.code }).open(); |
| | | } |
| | | |
| | | /** 导åºäºç»´ç ZIPï¼æå½åæ¥è¯¢æ¡ä»¶ççæç»/æ¹æ¬¡ï¼ */ |
| | | async function handleExportQr() { |
| | | const values = await gridApi.formApi.getValues(); |
| | | const params: { uuid?: string; batchId?: number } = {}; |
| | | if (values.uuid) { |
| | | params.uuid = values.uuid; |
| | | } |
| | | if (values.batchId) { |
| | | params.batchId = values.batchId; |
| | | } |
| | | if (!params.uuid && !params.batchId) { |
| | | message.warning("请å
æçæç»ææ¹æ¬¡çéåå导åºäºç»´ç "); |
| | | return; |
| | | } |
| | | const data = await exportBagQr(params); |
| | | downloadFileFromBlobPart({ fileName: "è¢ç äºç»´ç .zip", source: data }); |
| | | } |
| | | |
| | | /** 导åºè¢ç Excelï¼äº¤ä»å°å·ï¼ï¼å¯¼åºåè¢ç ç¶æå为已å°å·ï¼éå·æ°å表 */ |
| | | async function handleExport() { |
| | | const data = await exportBagCodeExcel(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: "è¢ç æ°æ®å
.xls", source: data }); |
| | | message.success("å¯¼åºæåï¼å¯¼åºçè¢ç å·²æ 记为已å°å·"); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** è·åå¾éçè¢ç è®°å½ */ |
| | | function getSelectedRecords(): MesProBagCodeApi.BagCode[] | undefined { |
| | | const records = gridApi.grid.getCheckboxRecords() as MesProBagCodeApi.BagCode[]; |
| | | if (records.length === 0) { |
| | | message.warning("请è³å°å¾é䏿¡è¢ç è®°å½"); |
| | | return undefined; |
| | | } |
| | | return records; |
| | | } |
| | | |
| | | /** æ¹éç»å®æç */ |
| | | function handleBindPallet() { |
| | | const records = getSelectedRecords(); |
| | | if (!records) return; |
| | | const batchIds = new Set(records.map((record) => record.batchId)); |
| | | if (batchIds.size > 1) { |
| | | message.warning("è¯·éæ©å䏿¹æ¬¡ä¸çè¢ç è¿è¡ç»å®"); |
| | | return; |
| | | } |
| | | if (records.some((record) => record.palletId)) { |
| | | message.warning("æéè¢ç ä¸åå¨å·²ç»å®æççè®°å½ï¼è¯·å
è§£ç»"); |
| | | return; |
| | | } |
| | | if ( |
| | | records.some( |
| | | (record) => record.status === MesProBagCodeStatusEnum.CANCELED |
| | | ) |
| | | ) { |
| | | message.warning("å·²ä½åºçè¢ç ä¸å
许ç»å®æç"); |
| | | return; |
| | | } |
| | | const first = records[0]!; |
| | | bindPalletFormModalApi |
| | | .setData({ |
| | | batchId: first.batchId, |
| | | batchCode: first.batchCode, |
| | | ids: records.map((record) => record.id!), |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** æ¹éè§£ç»æç */ |
| | | async function handleUnbindPallet() { |
| | | const records = getSelectedRecords(); |
| | | if (!records) return; |
| | | if (records.some((record) => !record.palletId)) { |
| | | message.warning("æéè¢ç ä¸å卿ªç»å®æççè®°å½ï¼æ æ³è§£ç»"); |
| | | return; |
| | | } |
| | | const palletIds = new Set(records.map((record) => record.palletId)); |
| | | if (palletIds.size > 1) { |
| | | message.warning("è¯·éæ©ç»å®å¨å䏿çä¸çè¢ç è¿è¡è§£ç»"); |
| | | return; |
| | | } |
| | | try { |
| | | await confirm(`ç¡®è®¤è§£ç»æé ${records.length} æ¡è¢ç åï¼`); |
| | | } catch { |
| | | return; |
| | | } |
| | | await unbindBagCodePallet({ |
| | | palletId: records[0]!.palletId!, |
| | | ids: records.map((record) => record.id!), |
| | | }); |
| | | message.success($t("ui.actionMessage.operationSuccess")); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** å é¤è¢ç çæç»ï¼æ°æ®å
ï¼ */ |
| | | async function handleDeleteGroup(row: MesProBagCodeApi.BagCode) { |
| | | await deleteBagCodesByUuid(row.uuid!); |
| | | message.success($t("ui.actionMessage.deleteSuccess", [row.uuid])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | checkboxConfig: { |
| | | highlight: true, |
| | | reserve: true, |
| | | }, |
| | | columns: useGridColumns(), |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getBagCodePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: "id", |
| | | isHover: true, |
| | | }, |
| | | showOverflow: true, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesProBagCodeApi.BagCode>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <GenerateFormModal @success="handleRefresh" /> |
| | | <BindPalletFormModal @success="handleRefresh" /> |
| | | <QrTraceQueryModal /> |
| | | <Grid table-title="è¢ç å表ï¼ä¸è¢ä¸ç ï¼"> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: 'çææ°æ®å
', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-bag-code:create'], |
| | | onClick: handleGenerate, |
| | | }, |
| | | { |
| | | label: 'ç»å®æç', |
| | | type: 'primary', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleBindPallet, |
| | | }, |
| | | { |
| | | label: 'è§£ç»æç', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleUnbindPallet, |
| | | }, |
| | | { |
| | | label: 'æ«ç 追溯', |
| | | icon: ACTION_ICON.SEARCH, |
| | | auth: ['mes:pro-bag-code:query'], |
| | | onClick: handleTrace, |
| | | }, |
| | | { |
| | | label: '导åºäºç»´ç ', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-bag-code:export'], |
| | | onClick: handleExportQr, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-bag-code:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: 'æ¥çäºç»´ç ', |
| | | type: 'link', |
| | | auth: ['mes:pro-bag-code:query'], |
| | | onClick: handleViewQr.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pro-bag-code:delete'], |
| | | ifShow: |
| | | row.status === MesProBagCodeStatusEnum.GENERATED && |
| | | !row.palletId, |
| | | popConfirm: { |
| | | title: |
| | | 'å é¤å°ç§»é¤è¯¥è¢ç æå±çæ´ä¸ªçæç»ï¼æ°æ®å
ï¼ï¼ç¡®è®¤å é¤åï¼', |
| | | confirm: handleDeleteGroup.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VbenFormSchema } from "#/adapter/form"; |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | import { MesProPalletStatusEnum } from "@vben/constants"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { bindBagCodePallet } from "#/api/mes/pro/bagCode"; |
| | | import { getPalletSimpleList } from "#/api/mes/pro/pallet"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | defineOptions({ name: "MesProBagCodeBindPallet" }); |
| | | |
| | | /** å¼¹çªä¼ å
¥æ°æ® */ |
| | | interface ModalData { |
| | | batchId: number; |
| | | batchCode?: string; |
| | | ids: number[]; |
| | | } |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | /** æå»ºè¡¨åï¼æç䏿é项ç±å¤é¨å 载注å
¥ï¼ */ |
| | | function buildSchema(palletOptions: { label: string; value: number }[]): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: "batchCode", |
| | | label: "çäº§æ¹æ¬¡", |
| | | component: "Input", |
| | | componentProps: { |
| | | disabled: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: "bagCount", |
| | | label: "ç»å®è¢æ°", |
| | | component: "InputNumber", |
| | | componentProps: { |
| | | class: "!w-full", |
| | | disabled: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: "palletId", |
| | | label: "ç®æ æç", |
| | | component: "Select", |
| | | componentProps: { |
| | | optionFilterProp: "label", |
| | | options: palletOptions, |
| | | placeholder: "è¯·éæ©ç®æ æç", |
| | | showSearch: true, |
| | | }, |
| | | rules: "selectRequired", |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: "w-full", |
| | | }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: "horizontal", |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | const data = modalApi.getData<ModalData>(); |
| | | const values = await formApi.getValues(); |
| | | modalApi.lock(); |
| | | try { |
| | | await bindBagCodePallet({ palletId: values.palletId, ids: data.ids }); |
| | | message.success($t("ui.actionMessage.operationSuccess")); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | const data = modalApi.getData<ModalData>(); |
| | | modalApi.lock(); |
| | | try { |
| | | // å è½½è¯¥æ¹æ¬¡ä¸çæçï¼æé¤å·²ä½åºï¼ |
| | | const pallets = await getPalletSimpleList(data.batchId); |
| | | const options = pallets |
| | | .filter( |
| | | (pallet) => pallet.status !== MesProPalletStatusEnum.CANCELED |
| | | ) |
| | | .map((pallet) => ({ |
| | | label: `${pallet.code}ï¼å·²è£
${pallet.bagCount ?? 0} è¢ / å¯è£
${ |
| | | pallet.capacity ?? "ä¸é" |
| | | } è¢ï¼`, |
| | | value: pallet.id!, |
| | | })); |
| | | await formApi.setState({ schema: buildSchema(options) }); |
| | | await formApi.setValues({ |
| | | bagCount: data.ids.length, |
| | | batchCode: data.batchCode, |
| | | }); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="è¢ç ç»å®æç" class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VbenFormSchema } from "#/adapter/form"; |
| | | import type { MesProBagCodeApi } from "#/api/mes/pro/bagCode"; |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | import { MesProBatchStatusEnum } from "@vben/constants"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { generateBagCodes } from "#/api/mes/pro/bagCode"; |
| | | import { getBatchSimpleList } from "#/api/mes/pro/batch"; |
| | | |
| | | defineOptions({ name: "MesProBagCodeGenerateForm" }); |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | /** æå»ºè¡¨åï¼æ¹æ¬¡ä¸æé项ç±å¤é¨å 载注å
¥ï¼ */ |
| | | function buildSchema(batchOptions: { label: string; value: number }[]): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: "batchId", |
| | | label: "çäº§æ¹æ¬¡", |
| | | component: "Select", |
| | | componentProps: { |
| | | options: batchOptions, |
| | | optionFilterProp: "label", |
| | | placeholder: "è¯·éæ©ç产ä¸çæ¹æ¬¡", |
| | | showSearch: true, |
| | | }, |
| | | rules: "selectRequired", |
| | | }, |
| | | { |
| | | fieldName: "count", |
| | | label: "çæè¢æ°", |
| | | component: "InputNumber", |
| | | componentProps: { |
| | | class: "!w-full", |
| | | min: 1, |
| | | placeholder: "请è¾å
¥éè¦çæçè¢ç æ°é", |
| | | precision: 0, |
| | | }, |
| | | rules: "required", |
| | | }, |
| | | { |
| | | fieldName: "remark", |
| | | label: "夿³¨", |
| | | component: "Textarea", |
| | | componentProps: { |
| | | placeholder: "请è¾å
¥å¤æ³¨", |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: "w-full", |
| | | }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: "horizontal", |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | modalApi.lock(); |
| | | try { |
| | | const values = |
| | | (await formApi.getValues()) as MesProBagCodeApi.GenerateParams; |
| | | await generateBagCodes(values); |
| | | message.success(`å·²æåçæ ${values.count} æ¡è¢ç `); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | modalApi.lock(); |
| | | try { |
| | | // ä»
ç产ä¸çæ¹æ¬¡å
许çæè¢ç |
| | | const batches = await getBatchSimpleList(); |
| | | const options = batches |
| | | .filter( |
| | | (batch) => batch.status === MesProBatchStatusEnum.PRODUCING |
| | | ) |
| | | .map((batch) => ({ |
| | | label: batch.code!, |
| | | value: batch.id!, |
| | | })); |
| | | await formApi.setState({ schema: buildSchema(options) }); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="çæè¢ç æ°æ®å
" class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProBatchApi } from '#/api/mes/pro/batch'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | import { ProLineSelect } from '#/views/mes/pro/line/components'; |
| | | import { UserSelect } from '#/views/system/user/components'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(formType: FormType): VbenFormSchema[] { |
| | | const disabled = formType === 'detail'; |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: 'æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: 'ä¿ååèªå¨çæ', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => formType !== 'create', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'æ¹æ¬¡ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | disabled: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PRO_BATCH_STATUS, 'number'), |
| | | }, |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => formType !== 'create', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'åç§', |
| | | component: markRaw(MdmItemSelect), |
| | | componentProps: { |
| | | disabled, |
| | | placeholder: 'è¯·éæ©åç§ï¼äº§ååç§ï¼', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'lineId', |
| | | label: '产线', |
| | | component: markRaw(ProLineSelect), |
| | | componentProps: { |
| | | disabled, |
| | | placeholder: 'è¯·éæ©äº§çº¿', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'produceDate', |
| | | label: 'çäº§æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | disabled, |
| | | format: 'YYYY-MM-DD HH:mm:ss', |
| | | placeholder: 'è¯·éæ©çäº§æ¥æ', |
| | | showTime: true, |
| | | valueFormat: 'YYYY-MM-DD HH:mm:ss', |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'planQuantity', |
| | | label: 'è®¡åæ°é', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | disabled, |
| | | min: 0, |
| | | placeholder: '请è¾å
¥è®¡åæ°é', |
| | | precision: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'ownerUserId', |
| | | label: '责任人', |
| | | component: markRaw(UserSelect), |
| | | componentProps: { |
| | | disabled, |
| | | placeholder: 'è¯·éæ©è´£ä»»äºº', |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-3', |
| | | componentProps: { |
| | | disabled, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æ¹æ¬¡å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: 'åç§', |
| | | component: markRaw(MdmItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©åç§', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'lineCode', |
| | | label: '产线ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥äº§çº¿ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'lineName', |
| | | label: '产线åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥äº§çº¿åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'ownerUserId', |
| | | label: '责任人', |
| | | component: markRaw(UserSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©è´£ä»»äºº', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'æ¹æ¬¡ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PRO_BATCH_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©æ¹æ¬¡ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'produceDate', |
| | | label: 'çäº§æ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesProBatchApi.Batch>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'æ¹æ¬¡å·', |
| | | fixed: 'left', |
| | | minWidth: 200, |
| | | slots: { default: 'code' }, |
| | | }, |
| | | { |
| | | field: 'itemCode', |
| | | title: 'åç§ç¼ç ', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'itemName', |
| | | title: 'åç§åç§°', |
| | | width: 140, |
| | | }, |
| | | { |
| | | field: 'lineCode', |
| | | title: '产线ç¼ç ', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'lineName', |
| | | title: '产线åç§°', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'produceDate', |
| | | title: 'çäº§æ¥æ', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | field: 'planQuantity', |
| | | title: 'è®¡åæ°é', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'ownerUserName', |
| | | title: '责任人', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'bagQuantity', |
| | | title: 'è¢ç æ°é', |
| | | width: 90, |
| | | }, |
| | | { |
| | | field: 'palletQuantity', |
| | | title: 'æçç æ°é', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'æ¹æ¬¡ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_BATCH_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 300, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç产人åé
ç½®å表çåæ®µï¼å¯ç¼è¾æï¼ */ |
| | | export function useWorkerEditGridColumns(): VxeTableGridOptions<MesProBatchApi.BatchWorker>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'userId', |
| | | title: 'ç产人å', |
| | | minWidth: 200, |
| | | slots: { default: 'userId' }, |
| | | }, |
| | | { |
| | | field: 'workerType', |
| | | title: 'å²ä½ç±»å', |
| | | width: 160, |
| | | slots: { default: 'workerType' }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 160, |
| | | slots: { default: 'remark' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 80, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç产人åé
ç½®å表çåæ®µï¼åªè¯»æï¼ */ |
| | | export function useWorkerReadGridColumns(): VxeTableGridOptions<MesProBatchApi.BatchWorker>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'userName', |
| | | title: '人åå§å', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'workerType', |
| | | title: 'å²ä½ç±»å', |
| | | width: 160, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_BATCH_WORKER_TYPE }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 160, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { MesProBatchApi } from "#/api/mes/pro/batch"; |
| | | |
| | | import { Page, useVbenModal } from "@vben/common-ui"; |
| | | import { MesProBatchStatusEnum } from "@vben/constants"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { Button, message } from "ant-design-vue"; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { |
| | | deleteBatch, |
| | | exportBatchExcel, |
| | | exportBatchQr, |
| | | getBatchPage, |
| | | updateBatchStatus, |
| | | } from "#/api/mes/pro/batch"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import Form from "./modules/form.vue"; |
| | | import QrTraceModal from "../components/qr-trace-modal.vue"; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [QrTraceQueryModal, qrTraceQueryModalApi] = useVbenModal({ |
| | | connectedComponent: QrTraceModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** æ«ç 追溯 */ |
| | | function handleTrace() { |
| | | qrTraceQueryModalApi.open(); |
| | | } |
| | | |
| | | /** æ¥çäºç»´ç ï¼è¡å
é¢å¡«ç å¼ï¼ */ |
| | | function handleViewQr(row: MesProBatchApi.Batch) { |
| | | qrTraceQueryModalApi.setData({ code: row.code }).open(); |
| | | } |
| | | |
| | | /** æå°æ´å¥äºç»´ç ï¼æ¹æ¬¡å¾ + å
¨é¨æçå¾ ZIPï¼ */ |
| | | async function handlePrintQrSet(row: MesProBatchApi.Batch) { |
| | | const data = await exportBatchQr(row.id!); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `æ¹æ¬¡äºç»´ç -${row.code}.zip`, |
| | | source: data, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢æ¹æ¬¡ */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: "create" }).open(); |
| | | } |
| | | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: MesProBatchApi.Batch) { |
| | | formModalApi.setData({ formType: "detail", id: row.id }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾æ¹æ¬¡ */ |
| | | function handleEdit(row: MesProBatchApi.Batch) { |
| | | formModalApi.setData({ formType: "update", id: row.id }).open(); |
| | | } |
| | | |
| | | /** å 餿¹æ¬¡ */ |
| | | async function handleDelete(row: MesProBatchApi.Batch) { |
| | | await deleteBatch(row.id!); |
| | | message.success($t("ui.actionMessage.deleteSuccess", [row.code])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** æ´æ°æ¹æ¬¡ç¶æ */ |
| | | async function handleUpdateStatus( |
| | | row: MesProBatchApi.Batch, |
| | | status: number, |
| | | statusName: string |
| | | ) { |
| | | await updateBatchStatus(row.id!, status); |
| | | message.success(`æ¹æ¬¡å·²${statusName}`); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportBatchExcel(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: "çäº§æ¹æ¬¡.xls", source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getBatchPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: "id", |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesProBatchApi.Batch>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <QrTraceQueryModal /> |
| | | <Grid table-title="çäº§æ¹æ¬¡å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['æ¹æ¬¡']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-batch:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'æ«ç 追溯', |
| | | icon: ACTION_ICON.SEARCH, |
| | | auth: ['mes:pro-batch:query'], |
| | | onClick: handleTrace, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-batch:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.code }} |
| | | </Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: 'æ¥çäºç»´ç ', |
| | | type: 'link', |
| | | auth: ['mes:pro-batch:query'], |
| | | onClick: handleViewQr.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pro-batch:update'], |
| | | ifShow: |
| | | row.status === MesProBatchStatusEnum.DRAFT || |
| | | row.status === MesProBatchStatusEnum.PRODUCING, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pro-batch:delete'], |
| | | ifShow: |
| | | row.status === MesProBatchStatusEnum.DRAFT || |
| | | row.status === MesProBatchStatusEnum.CANCELED, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.code]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | :drop-down-actions="[ |
| | | { |
| | | label: 'å¼å§ç产', |
| | | auth: ['mes:pro-batch:update'], |
| | | ifShow: row.status === MesProBatchStatusEnum.DRAFT, |
| | | popConfirm: { |
| | | title: '确认å¼å§çäº§è¯¥æ¹æ¬¡åï¼', |
| | | confirm: () => |
| | | handleUpdateStatus(row, MesProBatchStatusEnum.PRODUCING, 'å¼å§ç产'), |
| | | }, |
| | | }, |
| | | { |
| | | label: '宿', |
| | | auth: ['mes:pro-batch:update'], |
| | | ifShow: row.status === MesProBatchStatusEnum.PRODUCING, |
| | | popConfirm: { |
| | | title: 'ç¡®è®¤å®æè¯¥æ¹æ¬¡åï¼', |
| | | confirm: () => |
| | | handleUpdateStatus(row, MesProBatchStatusEnum.FINISHED, '宿'), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'ä½åº', |
| | | auth: ['mes:pro-batch:update'], |
| | | ifShow: |
| | | row.status === MesProBatchStatusEnum.DRAFT || |
| | | row.status === MesProBatchStatusEnum.PRODUCING, |
| | | popConfirm: { |
| | | title: '确认ä½åºè¯¥æ¹æ¬¡åï¼ä½åºåä¸å¯æ¢å¤ã', |
| | | confirm: () => |
| | | handleUpdateStatus(row, MesProBatchStatusEnum.CANCELED, 'ä½åº'), |
| | | }, |
| | | }, |
| | | { |
| | | label: 'æå°æ´å¥äºç»´ç ', |
| | | auth: ['mes:pro-batch:export'], |
| | | onClick: () => handlePrintQrSet(row), |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from "../data"; |
| | | |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { MesProBatchApi } from "#/api/mes/pro/batch"; |
| | | import type { SystemUserApi } from "#/api/system/user"; |
| | | |
| | | import { computed, nextTick, ref } from "vue"; |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | import { DICT_TYPE } from "@vben/constants"; |
| | | import { getDictOptions } from "@vben/hooks"; |
| | | |
| | | import { Button, Input, message, Select } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { createBatch, getBatch, updateBatch } from "#/api/mes/pro/batch"; |
| | | import { $t } from "#/locales"; |
| | | import { UserSelect } from "#/views/system/user/components"; |
| | | |
| | | import { |
| | | useFormSchema, |
| | | useWorkerEditGridColumns, |
| | | useWorkerReadGridColumns, |
| | | } from "../data"; |
| | | |
| | | defineOptions({ name: "MesProBatchForm" }); |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | const formType = ref<FormType>("create"); |
| | | const formData = ref<MesProBatchApi.Batch>(); |
| | | |
| | | const isEditable = computed(() => |
| | | ["create", "update"].includes(formType.value) |
| | | ); |
| | | |
| | | const getTitle = computed(() => { |
| | | switch (formType.value) { |
| | | case "detail": { |
| | | return $t("ui.actionTitle.view", ["çäº§æ¹æ¬¡"]); |
| | | } |
| | | case "update": { |
| | | return $t("ui.actionTitle.edit", ["çäº§æ¹æ¬¡"]); |
| | | } |
| | | default: { |
| | | return $t("ui.actionTitle.create", ["çäº§æ¹æ¬¡"]); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: "w-full", |
| | | }, |
| | | formItemClass: "col-span-1", |
| | | labelWidth: 110, |
| | | }, |
| | | layout: "horizontal", |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: "grid-cols-3", |
| | | }); |
| | | |
| | | // ========== ç产人åé
ç½®å表 ========== |
| | | const [WorkerGrid, workerGridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | autoResize: true, |
| | | border: true, |
| | | columns: useWorkerEditGridColumns(), |
| | | minHeight: 160, |
| | | pagerConfig: { enabled: false }, |
| | | rowConfig: { isHover: true, keyField: "_rowKey" }, |
| | | showOverflow: true, |
| | | toolbarConfig: { enabled: false }, |
| | | } as VxeTableGridOptions<MesProBatchApi.BatchWorker>, |
| | | }); |
| | | |
| | | /** æ°å¢è¡ç临æ¶è¡å·ï¼è´æ°èªå¢ï¼é¿å
ä¸ loadData ç index å²çªï¼ */ |
| | | let workerRowSeq = 0; |
| | | |
| | | /** æ·»å 人åè¡ */ |
| | | function handleAddWorker() { |
| | | workerGridApi.grid.insertAt({ _rowKey: -(++workerRowSeq) }, -1); |
| | | } |
| | | |
| | | /** å é¤äººåè¡ */ |
| | | function handleDeleteWorker(row: MesProBatchApi.BatchWorker) { |
| | | workerGridApi.grid.remove(row); |
| | | } |
| | | |
| | | /** 人åéæ©ç¼å·ååï¼åéåºæ¯ä¸å第ä¸ä¸ªå¼ï¼ */ |
| | | function handleWorkerUserIdChange( |
| | | value: number | number[] | undefined, |
| | | row: MesProBatchApi.BatchWorker |
| | | ) { |
| | | row.userId = Array.isArray(value) ? value[0] : value; |
| | | } |
| | | |
| | | /** 人åéæ©ååæ¶è®°å½å§åï¼ä¾¿äºæ ¡éªæç¤ºï¼ */ |
| | | function handleWorkerUserChange( |
| | | user: SystemUserApi.User | SystemUserApi.User[] | undefined, |
| | | row: MesProBatchApi.BatchWorker |
| | | ) { |
| | | row.userName = Array.isArray(user) ? user[0]?.nickname : user?.nickname; |
| | | } |
| | | |
| | | /** å²ä½ç±»ååå
¸é项 */ |
| | | const workerTypeOptions = computed(() => |
| | | getDictOptions(DICT_TYPE.MES_PRO_BATCH_WORKER_TYPE, "number").map( |
| | | (item) => ({ label: item.label, value: item.value as number }) |
| | | ) |
| | | ); |
| | | |
| | | /** æ ¡éªå¹¶è·å人åé
ç½®å表 */ |
| | | async function getValidWorkers(): Promise< |
| | | MesProBatchApi.BatchWorker[] | undefined |
| | | > { |
| | | // getData() åªè¿å loadData å è½½çæ°æ®ï¼insertAt æå
¥çè¡ééè¿ |
| | | // getInsertRecords() è·åï¼å¹¶æé¤ getRemoveRecords() ä¸å·²å é¤çè¡ |
| | | const data = workerGridApi.grid.getData() as Array< |
| | | MesProBatchApi.BatchWorker & { _rowKey?: number } |
| | | >; |
| | | const removeRecords = workerGridApi.grid.getRemoveRecords() as Array< |
| | | MesProBatchApi.BatchWorker & { _rowKey?: number } |
| | | >; |
| | | const insertRecords = workerGridApi.grid.getInsertRecords() as Array< |
| | | MesProBatchApi.BatchWorker & { _rowKey?: number } |
| | | >; |
| | | const rows = [ |
| | | ...data.filter( |
| | | (row) => !removeRecords.some((removed) => removed._rowKey === row._rowKey) |
| | | ), |
| | | ...insertRecords, |
| | | ].map(({ _rowKey, ...rest }) => rest); |
| | | if (rows.length === 0) { |
| | | message.warning("请è³å°é
ç½®ä¸åç产人å"); |
| | | return undefined; |
| | | } |
| | | for (const [index, row] of rows.entries()) { |
| | | if (!row.userId) { |
| | | message.warning(`è¯·éæ©ç¬¬ ${index + 1} è¡çç产人å`); |
| | | return undefined; |
| | | } |
| | | if (!row.workerType) { |
| | | message.warning(`è¯·éæ©ç¬¬ ${index + 1} è¡çå²ä½ç±»å`); |
| | | return undefined; |
| | | } |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | /** çå¾
人åå表 grid æè½½å®æï¼å¼¹çªæå¼ç¬é´ onOpenChange å
é¨è¡¨æ ¼å¯è½å°æªæè½½ï¼ */ |
| | | async function waitWorkerGridReady(): Promise<void> { |
| | | await nextTick(); |
| | | for (let i = 0; i < 100; i++) { |
| | | if (typeof workerGridApi.grid?.loadData === "function") { |
| | | return; |
| | | } |
| | | await new Promise((resolve) => setTimeout(resolve, 30)); |
| | | } |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | showCancelButton: false, // èªå®ä¹åæ¶/å
³éæé®ï¼ç¦ç¨é»è®¤åæ¶æé®é¿å
éå¤ |
| | | async onConfirm() { |
| | | if (!isEditable.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | const workers = await getValidWorkers(); |
| | | if (!workers) return; |
| | | modalApi.lock(); |
| | | try { |
| | | const data = (await formApi.getValues()) as MesProBatchApi.Batch; |
| | | if (formData.value?.id) { |
| | | await updateBatch({ ...data, id: formData.value.id, workers }); |
| | | } else { |
| | | await createBatch({ ...data, workers }); |
| | | } |
| | | message.success($t("ui.actionMessage.operationSuccess")); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useFormSchema(formType.value) }); |
| | | // æ ¹æ®è¡¨åæåæ¢äººåå表å |
| | | workerGridApi.setGridOptions({ |
| | | columns: isEditable.value |
| | | ? useWorkerEditGridColumns() |
| | | : useWorkerReadGridColumns(), |
| | | }); |
| | | if (data?.id) { |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getBatch(data.id); |
| | | await formApi.setValues(formData.value); |
| | | const workers = (formData.value.workers ?? []).map( |
| | | (worker, index) => ({ ...worker, _rowKey: worker.id ?? index }) |
| | | ); |
| | | await waitWorkerGridReady(); |
| | | await workerGridApi.grid.loadData(workers); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | } else { |
| | | await waitWorkerGridReady(); |
| | | await workerGridApi.grid.loadData([]); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" |
| | | class="w-3/5" |
| | | content-class="!max-h-[80vh] overflow-y-auto"> |
| | | <Form class="mx-4" /> |
| | | <div class="mx-4 mb-4"> |
| | | <div class="mb-2 flex items-center justify-between"> |
| | | <span class="font-medium">ç产人åé
ç½®</span> |
| | | <Button v-if="isEditable" size="small" type="primary" @click="handleAddWorker"> |
| | | æ·»å 人å |
| | | </Button> |
| | | </div> |
| | | <WorkerGrid> |
| | | <template #userId="{ row }"> |
| | | <UserSelect |
| | | :model-value="row.userId" |
| | | placeholder="è¯·éæ©ç产人å" |
| | | @update:model-value="handleWorkerUserIdChange($event, row)" |
| | | @change="handleWorkerUserChange($event, row)" |
| | | /> |
| | | </template> |
| | | <template #workerType="{ row }"> |
| | | <Select |
| | | v-model:value="row.workerType" |
| | | :options="workerTypeOptions" |
| | | class="w-full" |
| | | placeholder="è¯·éæ©å²ä½ç±»å" |
| | | /> |
| | | </template> |
| | | <template #remark="{ row }"> |
| | | <Input v-model:value="row.remark" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | onClick: handleDeleteWorker.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </WorkerGrid> |
| | | </div> |
| | | <template #append-footer> |
| | | <div class="flex gap-2"> |
| | | <Button @click="modalApi.close()"> |
| | | {{ isEditable ? 'åæ¶' : 'å
³é' }} |
| | | </Button> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProBagCodeApi } from "#/api/mes/pro/bagCode"; |
| | | import type { MesProBatchApi } from "#/api/mes/pro/batch"; |
| | | import type { MesProPalletApi } from "#/api/mes/pro/pallet"; |
| | | |
| | | import { ref } from "vue"; |
| | | |
| | | import { Barcode, useVbenModal } from "@vben/common-ui"; |
| | | import { DICT_TYPE } from "@vben/constants"; |
| | | import { getDictLabel } from "@vben/hooks"; |
| | | |
| | | import { Button, Empty, Input, message, Spin, Tag } from "ant-design-vue"; |
| | | |
| | | import { getBagCodeByCode } from "#/api/mes/pro/bagCode"; |
| | | import { getBatchByCode } from "#/api/mes/pro/batch"; |
| | | import { getPalletByCode } from "#/api/mes/pro/pallet"; |
| | | import { useDescription } from "#/components/description"; |
| | | |
| | | defineOptions({ name: "MesProQrTraceModal" }); |
| | | |
| | | type TraceType = "batch" | "pallet" | "bag"; |
| | | |
| | | const MAX_CODE_LENGTH = 64; |
| | | |
| | | const codeInput = ref<string>(); |
| | | const currentCode = ref<string>(); |
| | | const traceType = ref<TraceType>(); |
| | | const loading = ref(false); |
| | | const searched = ref(false); |
| | | const searchError = ref(false); |
| | | const bagTrace = ref<MesProBagCodeApi.BagCodeTrace>(); |
| | | const palletTrace = ref<MesProPalletApi.PalletTrace>(); |
| | | const batchTrace = ref<MesProBatchApi.BatchTrace>(); |
| | | const barcodeRef = ref<InstanceType<typeof Barcode>>(); |
| | | |
| | | const traceTypeLabel = ref(""); |
| | | |
| | | /** è¯å«äºç»´ç å
容类åï¼æ¹æ¬¡å· / æçç / è¢ç */ |
| | | function resolveCodeType(raw: string): TraceType | undefined { |
| | | const value = raw.trim().toUpperCase(); |
| | | if (/^.*-P\d{2}$/.test(value)) { |
| | | return "pallet"; |
| | | } |
| | | if (/^.*-\d{4}$/.test(value)) { |
| | | return "bag"; |
| | | } |
| | | return "batch"; |
| | | } |
| | | |
| | | function resetState() { |
| | | currentCode.value = undefined; |
| | | traceType.value = undefined; |
| | | traceTypeLabel.value = ""; |
| | | bagTrace.value = undefined; |
| | | palletTrace.value = undefined; |
| | | batchTrace.value = undefined; |
| | | searched.value = false; |
| | | searchError.value = false; |
| | | } |
| | | |
| | | /** æ«ç /è¾å
¥äºç»´ç å
容æ¥è¯¢è¿½æº¯ä¿¡æ¯ */ |
| | | async function handleSearch(raw?: string) { |
| | | const value = (raw ?? codeInput.value)?.trim().toUpperCase(); |
| | | if (!value) { |
| | | message.warning("è¯·æ«ææè¾å
¥äºç»´ç å
容"); |
| | | return; |
| | | } |
| | | if (value.length > MAX_CODE_LENGTH) { |
| | | message.warning("äºç»´ç å
容è¿é¿ï¼è¯·æ£æ¥æ¯å¦æ«ææ£ç¡®"); |
| | | return; |
| | | } |
| | | const type = resolveCodeType(value); |
| | | if (!type) { |
| | | message.warning("æ æ³è¯å«äºç»´ç ç±»å"); |
| | | return; |
| | | } |
| | | loading.value = true; |
| | | searched.value = true; |
| | | searchError.value = false; |
| | | currentCode.value = value; |
| | | traceType.value = undefined; |
| | | traceTypeLabel.value = ""; |
| | | bagTrace.value = undefined; |
| | | palletTrace.value = undefined; |
| | | batchTrace.value = undefined; |
| | | try { |
| | | if (type === "batch") { |
| | | batchTrace.value = await getBatchByCode(value); |
| | | traceTypeLabel.value = "æ¹æ¬¡å·"; |
| | | } else if (type === "pallet") { |
| | | palletTrace.value = await getPalletByCode(value); |
| | | traceTypeLabel.value = "æçç "; |
| | | } else { |
| | | bagTrace.value = await getBagCodeByCode(value); |
| | | traceTypeLabel.value = "è¢ç "; |
| | | } |
| | | traceType.value = type; |
| | | } catch { |
| | | searchError.value = true; |
| | | currentCode.value = undefined; |
| | | traceType.value = undefined; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | /** å½åè¿½æº¯æ°æ®çå
³é®ä¿¡æ¯ï¼ä¸è½½/æå°å¾çå å æåç¨ï¼ */ |
| | | function buildLabelText(): { label: string; value: string }[] { |
| | | if (traceType.value === "bag" && bagTrace.value) { |
| | | const d = bagTrace.value; |
| | | return [ |
| | | { label: "è¢ç ", value: d.code || "" }, |
| | | { label: "è¢åºå·", value: d.bagNo ? String(d.bagNo) : "" }, |
| | | { label: "æ¹æ¬¡å·", value: d.batchCode || "" }, |
| | | { label: "åç§", value: d.itemName || "" }, |
| | | { label: "è§æ ¼", value: d.itemSpecification || "" }, |
| | | { |
| | | label: "产线", |
| | | value: [d.lineCode, d.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | { label: "çäº§æ¥æ", value: d.produceDate || "" }, |
| | | { label: "责任人", value: d.ownerUserName || "" }, |
| | | ]; |
| | | } |
| | | if (traceType.value === "pallet" && palletTrace.value) { |
| | | const d = palletTrace.value; |
| | | return [ |
| | | { label: "æçç ", value: d.code || "" }, |
| | | { label: "æçåºå·", value: d.palletNo ? String(d.palletNo) : "" }, |
| | | { label: "æ¹æ¬¡å·", value: d.batchCode || "" }, |
| | | { label: "åç§", value: d.itemName || "" }, |
| | | { label: "è§æ ¼", value: d.itemSpecification || "" }, |
| | | { |
| | | label: "产线", |
| | | value: [d.lineCode, d.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | { label: "çäº§æ¥æ", value: d.produceDate || "" }, |
| | | { label: "责任人", value: d.ownerUserName || "" }, |
| | | ]; |
| | | } |
| | | if (batchTrace.value) { |
| | | const d = batchTrace.value; |
| | | return [ |
| | | { label: "æ¹æ¬¡å·", value: d.code || "" }, |
| | | { label: "åç§", value: d.itemName || "" }, |
| | | { label: "è§æ ¼", value: d.itemSpecification || "" }, |
| | | { |
| | | label: "产线", |
| | | value: [d.lineCode, d.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | { label: "çäº§æ¥æ", value: d.produceDate || "" }, |
| | | { label: "è®¡åæ°é", value: d.planQuantity ? String(d.planQuantity) : "" }, |
| | | { label: "责任人", value: d.ownerUserName || "" }, |
| | | ]; |
| | | } |
| | | return []; |
| | | } |
| | | |
| | | function escapeHtml(value: string) { |
| | | return value |
| | | .replaceAll("&", "&") |
| | | .replaceAll("<", "<") |
| | | .replaceAll(">", ">"); |
| | | } |
| | | |
| | | /** ä¸è½½äºç»´ç PNGï¼äºç»´ç + å
³é®ä¿¡æ¯æååæï¼ */ |
| | | function handleDownload() { |
| | | const base64 = barcodeRef.value?.getImageBase64(); |
| | | if (!base64) { |
| | | message.warning("äºç»´ç çæå¤±è´¥ï¼æ æ³ä¸è½½"); |
| | | return; |
| | | } |
| | | const items = buildLabelText(); |
| | | const canvas = document.createElement("canvas"); |
| | | const ctx = canvas.getContext("2d"); |
| | | if (!ctx) { |
| | | message.error("Canvas åå§å失败"); |
| | | return; |
| | | } |
| | | const img = new Image(); |
| | | img.onload = () => { |
| | | const padding = 20; |
| | | const infoX = img.width + padding * 2; |
| | | const infoWidth = 320; |
| | | canvas.width = infoX + infoWidth + padding; |
| | | canvas.height = Math.max(img.height + padding * 2, 300); |
| | | ctx.fillStyle = "#ffffff"; |
| | | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| | | ctx.drawImage(img, padding, padding); |
| | | ctx.fillStyle = "#000000"; |
| | | let y = padding + 10; |
| | | const lineHeight = 26; |
| | | items.forEach((item) => { |
| | | ctx.font = "bold 14px 'Microsoft YaHei', Arial, sans-serif"; |
| | | ctx.fillText(`${item.label}:`, infoX, y); |
| | | y += 18; |
| | | ctx.font = "14px 'Microsoft YaHei', Arial, sans-serif"; |
| | | const text = item.value || "-"; |
| | | const maxWidth = infoWidth - 10; |
| | | if (ctx.measureText(text).width > maxWidth) { |
| | | let currentText = text; |
| | | while (currentText.length > 0) { |
| | | let line = ""; |
| | | for (let i = 0; i < currentText.length; i++) { |
| | | const testLine = line + currentText[i]; |
| | | if (ctx.measureText(testLine).width > maxWidth) { |
| | | break; |
| | | } |
| | | line = testLine; |
| | | } |
| | | ctx.fillText(line, infoX + 5, y); |
| | | y += 16; |
| | | currentText = currentText.slice(line.length); |
| | | } |
| | | y += 6; |
| | | } else { |
| | | ctx.fillText(text, infoX + 5, y); |
| | | y += lineHeight; |
| | | } |
| | | }); |
| | | const link = document.createElement("a"); |
| | | link.href = canvas.toDataURL("image/png"); |
| | | link.download = `${currentCode.value || "qr"}_${Date.now()}.png`; |
| | | link.click(); |
| | | message.success("ä¸è½½æå"); |
| | | }; |
| | | img.src = base64; |
| | | } |
| | | |
| | | /** æå°äºç»´ç */ |
| | | function handlePrint() { |
| | | const base64 = barcodeRef.value?.getImageBase64(); |
| | | if (!base64) { |
| | | message.warning("äºç»´ç çæå¤±è´¥ï¼æ æ³æå°"); |
| | | return; |
| | | } |
| | | const items = buildLabelText(); |
| | | const printWindow = window.open("", "_blank"); |
| | | if (!printWindow) { |
| | | message.error("æ æ³æå¼æå°çªå£ï¼è¯·æ£æ¥æµè§å¨è®¾ç½®"); |
| | | return; |
| | | } |
| | | printWindow.document.write(`<!DOCTYPE html> |
| | | <html><head><meta charset="UTF-8"><title>æå°äºç»´ç </title> |
| | | <style>*{margin:0;padding:0}body{font-family:'Microsoft YaHei',Arial,sans-serif;padding:20px}.print-container{text-align:center}.qr-img{max-width:100%;margin:20px 0}.info{margin-top:20px;text-align:left;font-size:13px}.info p{margin:4px 0}@media print{body{padding:0}.print-container{padding:20px}}</style> |
| | | </head><body><div class="print-container"> |
| | | <img src="${base64}" class="qr-img" alt="äºç»´ç " /> |
| | | <div class="info"> |
| | | ${items.map((item) => `<p><strong>${escapeHtml(item.label)}:</strong> ${escapeHtml(item.value || "-")}</p>`).join("")} |
| | | </div></div></body></html>`); |
| | | printWindow.document.close(); |
| | | printWindow.addEventListener("load", () => { |
| | | setTimeout(() => printWindow.print(), 500); |
| | | }); |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ code?: string }>() || {}; |
| | | codeInput.value = data.code ?? undefined; |
| | | resetState(); |
| | | if (data.code) { |
| | | handleSearch(data.code); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | const [BagDescriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { |
| | | field: "code", |
| | | label: "è¢ç ï¼äºç»´ç å
容ï¼", |
| | | span: 2, |
| | | }, |
| | | { |
| | | field: "bagNo", |
| | | label: "è¢åºå·", |
| | | }, |
| | | { |
| | | field: "status", |
| | | label: "è¢ç ç¶æ", |
| | | render: (val) => |
| | | val ? getDictLabel(DICT_TYPE.MES_PRO_BAG_CODE_STATUS, val) : "", |
| | | }, |
| | | { |
| | | field: "batchCode", |
| | | label: "çäº§æ¹æ¬¡å·", |
| | | }, |
| | | { |
| | | field: "batchStatus", |
| | | label: "æ¹æ¬¡ç¶æ", |
| | | render: (val) => |
| | | val ? getDictLabel(DICT_TYPE.MES_PRO_BATCH_STATUS, val) : "", |
| | | }, |
| | | { |
| | | field: "itemName", |
| | | label: "åç§", |
| | | }, |
| | | { |
| | | field: "itemSpecification", |
| | | label: "è§æ ¼", |
| | | }, |
| | | { |
| | | field: "produceDate", |
| | | label: "çäº§æ¥æ", |
| | | }, |
| | | { |
| | | field: "ownerUserName", |
| | | label: "责任人", |
| | | }, |
| | | { |
| | | field: "lineCode", |
| | | label: "产线", |
| | | render: (val, data) => |
| | | [val, data?.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | { |
| | | field: "palletCode", |
| | | label: "ç»å®æçç ", |
| | | render: (val) => val || "æªç»å®", |
| | | }, |
| | | { |
| | | field: "printTime", |
| | | label: "导åºå°å·æ¶é´", |
| | | }, |
| | | ], |
| | | }); |
| | | |
| | | const [PalletDescriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { |
| | | field: "code", |
| | | label: "æçç ï¼äºç»´ç å
容ï¼", |
| | | span: 2, |
| | | }, |
| | | { |
| | | field: "palletNo", |
| | | label: "æçåºå·", |
| | | }, |
| | | { |
| | | field: "capacity", |
| | | label: "å¯è£
è¢æ°ï¼è¢/æçï¼", |
| | | }, |
| | | { |
| | | field: "bagCount", |
| | | label: "å·²è£
è¢æ°", |
| | | }, |
| | | { |
| | | field: "status", |
| | | label: "æçç¶æ", |
| | | render: (val) => |
| | | val ? getDictLabel(DICT_TYPE.MES_PRO_PALLET_STATUS, val) : "", |
| | | }, |
| | | { |
| | | field: "batchCode", |
| | | label: "çäº§æ¹æ¬¡å·", |
| | | }, |
| | | { |
| | | field: "batchStatus", |
| | | label: "æ¹æ¬¡ç¶æ", |
| | | render: (val) => |
| | | val ? getDictLabel(DICT_TYPE.MES_PRO_BATCH_STATUS, val) : "", |
| | | }, |
| | | { |
| | | field: "itemName", |
| | | label: "åç§", |
| | | }, |
| | | { |
| | | field: "itemSpecification", |
| | | label: "è§æ ¼", |
| | | }, |
| | | { |
| | | field: "produceDate", |
| | | label: "çäº§æ¥æ", |
| | | }, |
| | | { |
| | | field: "ownerUserName", |
| | | label: "责任人", |
| | | }, |
| | | { |
| | | field: "lineCode", |
| | | label: "产线", |
| | | render: (val, data) => |
| | | [val, data?.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | ], |
| | | }); |
| | | |
| | | const [BatchDescriptions] = useDescription({ |
| | | bordered: true, |
| | | column: 2, |
| | | schema: [ |
| | | { |
| | | field: "code", |
| | | label: "æ¹æ¬¡å·ï¼äºç»´ç å
容ï¼", |
| | | span: 2, |
| | | }, |
| | | { |
| | | field: "status", |
| | | label: "æ¹æ¬¡ç¶æ", |
| | | render: (val) => |
| | | val ? getDictLabel(DICT_TYPE.MES_PRO_BATCH_STATUS, val) : "", |
| | | }, |
| | | { |
| | | field: "itemName", |
| | | label: "åç§", |
| | | }, |
| | | { |
| | | field: "itemSpecification", |
| | | label: "è§æ ¼", |
| | | }, |
| | | { |
| | | field: "lineCode", |
| | | label: "产线", |
| | | render: (val, data) => |
| | | [val, data?.lineName].filter(Boolean).join(" / "), |
| | | }, |
| | | { |
| | | field: "produceDate", |
| | | label: "çäº§æ¥æ", |
| | | }, |
| | | { |
| | | field: "planQuantity", |
| | | label: "è®¡åæ°é", |
| | | }, |
| | | { |
| | | field: "bagQuantity", |
| | | label: "å·²çæè¢ç æ°é", |
| | | }, |
| | | { |
| | | field: "palletQuantity", |
| | | label: "å·²çææçç æ°é", |
| | | }, |
| | | { |
| | | field: "ownerUserName", |
| | | label: "责任人", |
| | | }, |
| | | { |
| | | field: "remark", |
| | | label: "夿³¨", |
| | | }, |
| | | ], |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | :show-confirm-button="false" |
| | | cancel-text="å
³é" |
| | | class="w-3/5" |
| | | title="æ«ç 追溯 / æ¥çäºç»´ç " |
| | | > |
| | | <div class="mx-4"> |
| | | <div class="mb-4 flex gap-2"> |
| | | <Input |
| | | v-model:value="codeInput" |
| | | allow-clear |
| | | class="!w-96" |
| | | placeholder="è¯·æ«ææè¾å
¥æ¹æ¬¡å· / æçç / è¢ç ï¼å车æ¥è¯¢" |
| | | @press-enter="() => handleSearch()" |
| | | /> |
| | | <Button :loading="loading" type="primary" @click="() => handleSearch()"> |
| | | æ¥è¯¢ |
| | | </Button> |
| | | </div> |
| | | <Spin :spinning="loading"> |
| | | <template v-if="currentCode && traceType"> |
| | | <div |
| | | class="mb-4 flex items-center gap-6 rounded bg-gray-100 p-5" |
| | | > |
| | | <div class="flex-shrink-0"> |
| | | <Barcode ref="barcodeRef" :content="currentCode" :width="200" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <p class="text-lg font-bold break-all">{{ currentCode }}</p> |
| | | <p class="mt-1 text-xs text-gray-500"> |
| | | ç ç±»åï¼{{ traceTypeLabel }} |
| | | </p> |
| | | </div> |
| | | </div> |
| | | <template v-if="traceType === 'batch' && batchTrace"> |
| | | <BatchDescriptions :data="batchTrace" class="mt-2" /> |
| | | <div |
| | | v-if="batchTrace.workers?.length" |
| | | class="mt-3 rounded border border-solid border-gray-200 p-3" |
| | | > |
| | | <p class="mb-2 font-bold"> |
| | | ç产人åï¼{{ batchTrace.workers.length }}ï¼ |
| | | </p> |
| | | <div class="flex flex-wrap gap-2"> |
| | | <Tag v-for="w in batchTrace.workers" :key="w.id"> |
| | | {{ w.userName |
| | | }}<template v-if="w.workerType" |
| | | >ï¼{{ |
| | | getDictLabel(DICT_TYPE.MES_PRO_BATCH_WORKER_TYPE, w.workerType) |
| | | }}ï¼</template |
| | | > |
| | | </Tag> |
| | | </div> |
| | | </div> |
| | | <div |
| | | v-if="batchTrace.pallets?.length" |
| | | class="mt-3 rounded border border-solid border-gray-200 p-3" |
| | | > |
| | | <p class="mb-2 font-bold"> |
| | | æçç ï¼{{ batchTrace.pallets.length }}ï¼ |
| | | </p> |
| | | <div class="flex flex-wrap gap-2"> |
| | | <Tag v-for="p in batchTrace.pallets" :key="p.id"> |
| | | {{ p.code }} |
| | | </Tag> |
| | | </div> |
| | | </div> |
| | | <div |
| | | v-if="batchTrace.bags?.length" |
| | | class="mt-3 rounded border border-solid border-gray-200 p-3" |
| | | > |
| | | <p class="mb-2 font-bold"> |
| | | è¢ç |
| | | <span |
| | | v-if=" |
| | | batchTrace.bagTotal != null && |
| | | batchTrace.bagTotal > batchTrace.bags.length |
| | | " |
| | | class="text-xs text-gray-400" |
| | | > |
| | | ï¼ä»
å±ç¤ºå {{ batchTrace.bags.length }} æ¡ï¼å
± |
| | | {{ batchTrace.bagTotal }} æ¡ï¼ |
| | | </span> |
| | | </p> |
| | | <div class="flex max-h-48 flex-wrap gap-2 overflow-y-auto"> |
| | | <Tag v-for="b in batchTrace.bags" :key="b.id"> |
| | | {{ b.code }} |
| | | </Tag> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <BagDescriptions |
| | | v-else-if="traceType === 'bag' && bagTrace" |
| | | :data="bagTrace" |
| | | class="mt-2" |
| | | /> |
| | | <PalletDescriptions |
| | | v-else-if="traceType === 'pallet' && palletTrace" |
| | | :data="palletTrace" |
| | | class="mt-2" |
| | | /> |
| | | </template> |
| | | <Empty |
| | | v-else-if="searched" |
| | | :description="searchError ? '该ç ä¸å卿已å é¤' : 'æªæ¥è¯¢å°è¿½æº¯ä¿¡æ¯'" |
| | | /> |
| | | </Spin> |
| | | </div> |
| | | <template v-if="currentCode && traceType" #append-footer> |
| | | <div class="flex gap-2"> |
| | | <Button type="primary" @click="handlePrint">æå°</Button> |
| | | <Button @click="handleDownload">ä¸è½½äºç»´ç </Button> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | export { default as ProLineSelect } from './select.vue'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesProLineApi } from '#/api/mes/pro/line'; |
| | | |
| | | import { computed, onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Select, Tag, Tooltip } from 'ant-design-vue'; |
| | | |
| | | import { getLineSimpleList } from '#/api/mes/pro/line'; |
| | | |
| | | defineOptions({ name: 'ProLineSelect', inheritAttrs: false }); |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | disabled?: boolean; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | }>(), |
| | | { |
| | | allowClear: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©äº§çº¿', |
| | | }, |
| | | ); |
| | | |
| | | const emit = defineEmits<{ |
| | | change: [item: MesProLineApi.Line | undefined]; |
| | | 'update:modelValue': [value: number | undefined]; |
| | | }>(); |
| | | |
| | | const allList = ref<MesProLineApi.Line[]>([]); |
| | | const selectedItem = ref<MesProLineApi.Line>(); |
| | | |
| | | const selectValue = computed({ |
| | | get: () => props.modelValue, |
| | | set: (value: number | undefined) => { |
| | | emit('update:modelValue', value); |
| | | }, |
| | | }); |
| | | |
| | | /** åç«¯è¿æ»¤ï¼æäº§çº¿åç§°æç¼ç 模ç³å¹é
*/ |
| | | function handleFilter(input: string, option: any) { |
| | | const keyword = input.toLowerCase(); |
| | | const item = option?.item as MesProLineApi.Line | undefined; |
| | | return Boolean( |
| | | item?.name?.toLowerCase().includes(keyword) || |
| | | item?.code?.toLowerCase().includes(keyword), |
| | | ); |
| | | } |
| | | |
| | | /** æ ¹æ®å½åå¼åæ¥ tooltip å±ç¤ºç产线详æ
*/ |
| | | function syncSelectedItem(value: number | undefined) { |
| | | selectedItem.value = |
| | | value === undefined |
| | | ? undefined |
| | | : allList.value.find((item) => item.id === value); |
| | | } |
| | | |
| | | /** é¤ v-model å¤ï¼é¢å¤æåºå®æ´äº§çº¿å¯¹è±¡ç»ä¸å¡è¡¨åä½¿ç¨ */ |
| | | function handleChange(value: any) { |
| | | const nextValue = value === undefined ? undefined : Number(value); |
| | | syncSelectedItem(nextValue); |
| | | emit('change', selectedItem.value); |
| | | } |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (value) => { |
| | | syncSelectedItem(value); |
| | | }, |
| | | ); |
| | | |
| | | onMounted(async () => { |
| | | allList.value = await getLineSimpleList(); |
| | | syncSelectedItem(props.modelValue); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false"> |
| | | <template #title> |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>ç¼ç ï¼{{ selectedItem.code || '-' }}</div> |
| | | <div>åç§°ï¼{{ selectedItem.name || '-' }}</div> |
| | | <div>夿³¨ï¼{{ selectedItem.remark || '-' }}</div> |
| | | </div> |
| | | </template> |
| | | <Select |
| | | v-bind="$attrs" |
| | | v-model:value="selectValue" |
| | | :allow-clear="allowClear" |
| | | :disabled="disabled" |
| | | :filter-option="handleFilter" |
| | | :placeholder="placeholder" |
| | | class="w-full" |
| | | show-search |
| | | @change="handleChange" |
| | | > |
| | | <Select.Option |
| | | v-for="item in allList" |
| | | :key="item.id" |
| | | :item="item" |
| | | :value="item.id" |
| | | > |
| | | <div class="flex items-center gap-2"> |
| | | <span>{{ item.name }}</span> |
| | | <Tag v-if="item.code" color="default">{{ item.code }}</Tag> |
| | | </div> |
| | | </Select.Option> |
| | | </Select> |
| | | </Tooltip> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProLineApi } from '#/api/mes/pro/line'; |
| | | |
| | | import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'create' | 'detail' | 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹äº§çº¿ç表å */ |
| | | export function useFormSchema(formType: FormType): VbenFormSchema[] { |
| | | const disabled = formType === 'detail'; |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'code', |
| | | label: '产线ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled, |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥äº§çº¿ç¼ç ï¼å䏿¹æ¬¡å·çæï¼', |
| | | }, |
| | | rules: z.string().min(1, '产线ç¼ç ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '产线åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled, |
| | | maxLength: 100, |
| | | placeholder: '请è¾å
¥äº§çº¿åç§°', |
| | | }, |
| | | rules: z.string().min(1, '产线åç§°ä¸è½ä¸ºç©º').max(100), |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | buttonStyle: 'solid', |
| | | disabled, |
| | | optionType: 'button', |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | }, |
| | | defaultValue: CommonStatusEnum.ENABLE, |
| | | rules: z.number().default(CommonStatusEnum.ENABLE), |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | disabled, |
| | | maxLength: 250, |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: '产线ç¼ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥äº§çº¿ç¼ç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '产线åç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥äº§çº¿åç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesProLineApi.Line>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: '产线ç¼ç ', |
| | | minWidth: 150, |
| | | slots: { default: 'code' }, |
| | | }, |
| | | { field: 'name', title: '产线åç§°', minWidth: 180 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.COMMON_STATUS }, |
| | | }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 180 }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 180, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { MesProLineApi } from "#/api/mes/pro/line"; |
| | | |
| | | import { Page, useVbenModal } from "@vben/common-ui"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { Button, message } from "ant-design-vue"; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { |
| | | deleteLine, |
| | | exportLineExcel, |
| | | getLinePage, |
| | | } from "#/api/mes/pro/line"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import Form from "./modules/form.vue"; |
| | | |
| | | defineOptions({ name: "MesProLine" }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** æ°å¢äº§çº¿ */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: "create" }).open(); |
| | | } |
| | | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: MesProLineApi.Line) { |
| | | formModalApi.setData({ formType: "detail", id: row.id }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾äº§çº¿ */ |
| | | function handleEdit(row: MesProLineApi.Line) { |
| | | formModalApi.setData({ formType: "update", id: row.id }).open(); |
| | | } |
| | | |
| | | /** å é¤äº§çº¿ */ |
| | | async function handleDelete(row: MesProLineApi.Line) { |
| | | await deleteLine(row.id!); |
| | | message.success($t("ui.actionMessage.deleteSuccess", [row.name])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportLineExcel(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: "产线维æ¤.xls", source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getLinePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: "id", |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesProLineApi.Line>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="产线å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['产线']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-line:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-line:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | <template #code="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.code }} |
| | | </Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:pro-line:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pro-line:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { FormType } from "../data"; |
| | | import type { MesProLineApi } from "#/api/mes/pro/line"; |
| | | |
| | | import { computed, ref } from "vue"; |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | |
| | | import { Button, message } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { createLine, getLine, updateLine } from "#/api/mes/pro/line"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useFormSchema } from "../data"; |
| | | |
| | | defineOptions({ name: "MesProLineForm" }); |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | const formType = ref<FormType>("create"); |
| | | const formData = ref<MesProLineApi.Line>(); |
| | | |
| | | const isEditable = computed(() => |
| | | ["create", "update"].includes(formType.value) |
| | | ); |
| | | |
| | | const getTitle = computed(() => { |
| | | switch (formType.value) { |
| | | case "detail": { |
| | | return $t("ui.actionTitle.view", ["产线"]); |
| | | } |
| | | case "update": { |
| | | return $t("ui.actionTitle.edit", ["产线"]); |
| | | } |
| | | default: { |
| | | return $t("ui.actionTitle.create", ["产线"]); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: "w-full", |
| | | }, |
| | | formItemClass: "col-span-1", |
| | | labelWidth: 100, |
| | | }, |
| | | layout: "horizontal", |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: "grid-cols-2", |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!isEditable.value) { |
| | | await modalApi.close(); |
| | | return; |
| | | } |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | modalApi.lock(); |
| | | try { |
| | | const data = (await formApi.getValues()) as MesProLineApi.Line; |
| | | if (formData.value?.id) { |
| | | await updateLine({ ...data, id: formData.value.id }); |
| | | } else { |
| | | await createLine(data); |
| | | } |
| | | message.success($t("ui.actionMessage.operationSuccess")); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ formType: FormType; id?: number }>(); |
| | | formType.value = data.formType; |
| | | formApi.setState({ schema: useFormSchema(formType.value) }); |
| | | if (data?.id) { |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getLine(data.id); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | <template #append-footer> |
| | | <div class="flex gap-2"> |
| | | <Button @click="modalApi.close()"> |
| | | {{ isEditable ? 'åæ¶' : 'å
³é' }} |
| | | </Button> |
| | | </div> |
| | | </template> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProPalletApi } from '#/api/mes/pro/pallet'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'æçç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æçç ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'batchCode', |
| | | label: 'æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥æ¹æ¬¡å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'æçç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions(DICT_TYPE.MES_PRO_PALLET_STATUS, 'number'), |
| | | placeholder: 'è¯·éæ©æçç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesProPalletApi.Pallet>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'æçç ', |
| | | fixed: 'left', |
| | | minWidth: 210, |
| | | }, |
| | | { |
| | | field: 'palletNo', |
| | | title: 'æçåºå·', |
| | | width: 90, |
| | | }, |
| | | { |
| | | field: 'batchCode', |
| | | title: 'æ¹æ¬¡å·', |
| | | minWidth: 190, |
| | | }, |
| | | { |
| | | field: 'capacity', |
| | | title: 'å¯è£
è¢æ°ï¼è¢/æçï¼', |
| | | width: 150, |
| | | }, |
| | | { |
| | | field: 'bagCount', |
| | | title: 'å·²ç»è¢ç æ°é', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: 'æçç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_PALLET_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | title: '夿³¨', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 170, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from "#/adapter/vxe-table"; |
| | | import type { MesProPalletApi } from "#/api/mes/pro/pallet"; |
| | | |
| | | import { Page, useVbenModal } from "@vben/common-ui"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { |
| | | deletePallet, |
| | | exportPalletExcel, |
| | | exportPalletQr, |
| | | getPalletPage, |
| | | } from "#/api/mes/pro/pallet"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | | import GenerateModal from "./modules/generate-form.vue"; |
| | | import QrTraceModal from "../components/qr-trace-modal.vue"; |
| | | |
| | | defineOptions({ name: "MesProPallet" }); |
| | | |
| | | const [GenerateFormModal, generateFormModalApi] = useVbenModal({ |
| | | connectedComponent: GenerateModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [QrTraceQueryModal, qrTraceQueryModalApi] = useVbenModal({ |
| | | connectedComponent: QrTraceModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** æ¹éçææçç */ |
| | | function handleCreate() { |
| | | generateFormModalApi.open(); |
| | | } |
| | | |
| | | /** æ«ç 追溯 */ |
| | | function handleTrace() { |
| | | qrTraceQueryModalApi.open(); |
| | | } |
| | | |
| | | /** æ¥çäºç»´ç ï¼è¡å
é¢å¡«ç å¼ï¼ */ |
| | | function handleViewQr(row: MesProPalletApi.Pallet) { |
| | | qrTraceQueryModalApi.setData({ code: row.code }).open(); |
| | | } |
| | | |
| | | /** 导åºäºç»´ç ZIPï¼æå½åæ¥è¯¢æ¡ä»¶çæ¹æ¬¡ï¼ */ |
| | | async function handleExportQr() { |
| | | const values = await gridApi.formApi.getValues(); |
| | | if (!values.batchId) { |
| | | message.warning("请å
ææ¹æ¬¡çéåå导åºäºç»´ç "); |
| | | return; |
| | | } |
| | | const data = await exportPalletQr(values.batchId); |
| | | downloadFileFromBlobPart({ fileName: "æçç äºç»´ç .zip", source: data }); |
| | | } |
| | | |
| | | /** å 餿çç */ |
| | | async function handleDelete(row: MesProPalletApi.Pallet) { |
| | | await deletePallet(row.id!); |
| | | message.success($t("ui.actionMessage.deleteSuccess", [row.code])); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportPalletExcel(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: "æçç .xls", source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: "auto", |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getPalletPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: "id", |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesProPalletApi.Pallet>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <GenerateFormModal @success="handleRefresh" /> |
| | | <QrTraceQueryModal /> |
| | | <Grid table-title="æçç å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: 'æ¹éçæ', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-pallet:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'æ«ç 追溯', |
| | | icon: ACTION_ICON.SEARCH, |
| | | auth: ['mes:pro-pallet:query'], |
| | | onClick: handleTrace, |
| | | }, |
| | | { |
| | | label: '导åºäºç»´ç ', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-pallet:export'], |
| | | onClick: handleExportQr, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-pallet:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: 'æ¥çäºç»´ç ', |
| | | type: 'link', |
| | | auth: ['mes:pro-pallet:query'], |
| | | onClick: handleViewQr.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:pro-pallet:delete'], |
| | | ifShow: !row.bagCount, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.code]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VbenFormSchema } from "#/adapter/form"; |
| | | import type { MesProPalletApi } from "#/api/mes/pro/pallet"; |
| | | |
| | | import { useVbenModal } from "@vben/common-ui"; |
| | | import { MesProBatchStatusEnum } from "@vben/constants"; |
| | | |
| | | import { message } from "ant-design-vue"; |
| | | |
| | | import { useVbenForm } from "#/adapter/form"; |
| | | import { getBatchSimpleList } from "#/api/mes/pro/batch"; |
| | | import { batchCreatePallet } from "#/api/mes/pro/pallet"; |
| | | |
| | | defineOptions({ name: "MesProPalletGenerateForm" }); |
| | | |
| | | const emit = defineEmits(["success"]); |
| | | |
| | | /** æå»ºè¡¨åï¼æ¹æ¬¡ä¸æé项ç±å¤é¨å 载注å
¥ï¼ */ |
| | | function buildSchema(batchOptions: { label: string; value: number }[]): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: "batchId", |
| | | label: "çäº§æ¹æ¬¡", |
| | | component: "Select", |
| | | componentProps: { |
| | | options: batchOptions, |
| | | optionFilterProp: "label", |
| | | placeholder: "è¯·éæ©ç产ä¸çæ¹æ¬¡", |
| | | showSearch: true, |
| | | }, |
| | | rules: "selectRequired", |
| | | }, |
| | | { |
| | | fieldName: "count", |
| | | label: "çææ°é", |
| | | component: "InputNumber", |
| | | componentProps: { |
| | | class: "!w-full", |
| | | min: 1, |
| | | placeholder: "请è¾å
¥éè¦çæçæçæ°é", |
| | | precision: 0, |
| | | }, |
| | | rules: "required", |
| | | }, |
| | | { |
| | | fieldName: "capacity", |
| | | label: "å¯è£
è¢æ°", |
| | | component: "InputNumber", |
| | | componentProps: { |
| | | class: "!w-full", |
| | | min: 1, |
| | | placeholder: "请è¾å
¥æ¯æçå¯è£
è¢æ°ï¼è¢/æçï¼", |
| | | precision: 0, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: "remark", |
| | | label: "夿³¨", |
| | | component: "Textarea", |
| | | componentProps: { |
| | | placeholder: "请è¾å
¥å¤æ³¨", |
| | | rows: 2, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: "w-full", |
| | | }, |
| | | labelWidth: 100, |
| | | }, |
| | | layout: "horizontal", |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) return; |
| | | modalApi.lock(); |
| | | try { |
| | | const values = |
| | | (await formApi.getValues()) as MesProPalletApi.BatchCreateParams; |
| | | const ids = await batchCreatePallet(values); |
| | | message.success(`å·²æåçæ ${ids.length} 个æçç `); |
| | | emit("success"); |
| | | await modalApi.close(); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) return; |
| | | modalApi.lock(); |
| | | try { |
| | | // ä»
ç产ä¸çæ¹æ¬¡å
è®¸çææçç |
| | | const batches = await getBatchSimpleList(); |
| | | const options = batches |
| | | .filter( |
| | | (batch) => batch.status === MesProBatchStatusEnum.PRODUCING |
| | | ) |
| | | .map((batch) => ({ |
| | | label: batch.code!, |
| | | value: batch.id!, |
| | | })); |
| | | await formApi.setState({ schema: buildSchema(options) }); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="æ¹éçææçç " class="w-2/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |