| | |
| | | .DS_Store |
| | | node_modules/ |
| | | dist/ |
| | | .local-backups/ |
| | | npm-debug.log* |
| | | yarn-debug.log* |
| | | yarn-error.log* |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 客æ·ç³æ¥ä¸é¡¹ç®ä¸å¡åçº§èæ¬ |
| | | -- éç¨åºï¼product-inventory-management-xmgl |
| | | -- æ§è¡åå¿
é¡»å
åå
¨åºå¤ä»½ï¼æ¬èæ¬ä¸å é¤ç°æè¡¨åæ°æ®ã |
| | | -- å»ºè®®å¨æµè¯åºéªè¯ååæ§è¡ç产åºã |
| | | |
| | | SET NAMES utf8mb4; |
| | | SET FOREIGN_KEY_CHECKS = 0; |
| | | |
| | | CREATE TABLE IF NOT EXISTS `customer_submission` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | `customer_id` bigint NOT NULL COMMENT '客æ·ID', |
| | | `report_year` int NOT NULL COMMENT 'æ¥å年度', |
| | | `report_name` varchar(255) DEFAULT NULL COMMENT 'ææåç§°', |
| | | `material_files` text COMMENT 'å®¢æ·ææéä»¶ï¼éå·åé', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT '0å¾
å®¡æ ¸ 1å¤çä¸ 2å·²åå¸ 3å·²éå', |
| | | `result_text` varchar(2000) DEFAULT NULL COMMENT 'åå¸ç»æè¯´æ', |
| | | `result_files` text COMMENT 'ç»æéä»¶ï¼éå·åé', |
| | | `review_remark` varchar(1000) DEFAULT NULL COMMENT 'å®¡æ ¸æè§', |
| | | `submit_user_id` bigint DEFAULT NULL COMMENT 'æäº¤äººID', |
| | | `submit_user_name` varchar(100) DEFAULT NULL COMMENT 'æäº¤äººåç§°', |
| | | `publish_user_id` bigint DEFAULT NULL COMMENT 'åå¸äººID', |
| | | `publish_user_name` varchar(100) DEFAULT NULL COMMENT 'åå¸äººåç§°', |
| | | `publish_time` datetime DEFAULT NULL COMMENT 'å叿¶é´', |
| | | `tenant_id` bigint DEFAULT NULL COMMENT 'ç§æ·ID', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨ID', |
| | | `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT 'å 餿 è®°', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_customer_report_year` (`customer_id`,`report_year`,`is_deleted`), |
| | | KEY `idx_submission_status` (`status`), |
| | | KEY `idx_submission_user` (`submit_user_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客æ·å¹´åº¦ç³æ¥ææä¸ç»æ'; |
| | | |
| | | DROP PROCEDURE IF EXISTS `add_column_if_missing`; |
| | | DELIMITER $$ |
| | | CREATE PROCEDURE `add_column_if_missing`( |
| | | IN p_table varchar(64), IN p_column varchar(64), IN p_definition varchar(1000) |
| | | ) |
| | | BEGIN |
| | | IF NOT EXISTS ( |
| | | SELECT 1 FROM information_schema.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column |
| | | ) THEN |
| | | SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition); |
| | | PREPARE stmt FROM @ddl; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | | END IF; |
| | | END$$ |
| | | DELIMITER ; |
| | | |
| | | CALL add_column_if_missing('sales_ledger', 'business_approval_status', "tinyint NOT NULL DEFAULT 0 COMMENT '0è稿 1å¾
å®¡æ¹ 2å·²éè¿ 3å·²éå'"); |
| | | CALL add_column_if_missing('sales_ledger', 'business_approval_remark', "varchar(1000) DEFAULT NULL COMMENT 'å®¡æ¹æè§'"); |
| | | CALL add_column_if_missing('sales_ledger', 'business_approved_by', "bigint DEFAULT NULL COMMENT '审æ¹äººID'"); |
| | | CALL add_column_if_missing('sales_ledger', 'business_approved_name', "varchar(100) DEFAULT NULL COMMENT '审æ¹äººåç§°'"); |
| | | CALL add_column_if_missing('sales_ledger', 'business_approved_time', "datetime DEFAULT NULL COMMENT 'å®¡æ¹æ¶é´'"); |
| | | CALL add_column_if_missing('sales_ledger', 'business_submitted_time', "datetime DEFAULT NULL COMMENT 'æäº¤å®¡æ¹æ¶é´'"); |
| | | CALL add_column_if_missing('sales_ledger', 'project_management_info_id', "bigint DEFAULT NULL COMMENT '审æ¹éè¿åçæç项ç®ID'"); |
| | | DROP PROCEDURE IF EXISTS `add_column_if_missing`; |
| | | |
| | | -- åºå®å
é¶æ®µæ¨¡æ¿ï¼é夿§è¡ä¸ä¼éå¤å建ã |
| | | INSERT INTO `project_management_plan` (`name`, `description`, `is_delete`, `create_time`, `update_time`, `create_user_name`) |
| | | SELECT '项ç®ç³æ¥å
é¶æ®µ', 'ä¸å¡å®¡æ¹éè¿åèªå¨çæï¼ç«é¡¹ãå¢éåå·¥ãèµææ±æ¥ãç³æ¥ãç³æ¥ç»æãéªæ¶', 0, NOW(), NOW(), 'åçº§èæ¬' |
| | | WHERE NOT EXISTS ( |
| | | SELECT 1 FROM `project_management_plan` WHERE `name` = '项ç®ç³æ¥å
é¶æ®µ' AND `is_delete` = 0 |
| | | ); |
| | | SET @plan_id = (SELECT `id` FROM `project_management_plan` WHERE `name` = '项ç®ç³æ¥å
é¶æ®µ' AND `is_delete` = 0 ORDER BY `id` LIMIT 1); |
| | | |
| | | INSERT INTO `project_management_plan_node` |
| | | (`project_management_plan_id`, `sort`, `name`, `estimated_duration`, `work_content`, `is_delete`, `create_time`, `update_time`, `create_user_name`) |
| | | SELECT @plan_id, 1, 'ç«é¡¹', 1, '建ç«é¡¹ç®å¹¶ç¡®è®¤è´è´£äººã计å卿ååºç¡èµæ', 0, NOW(), NOW(), 'åçº§èæ¬' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='ç«é¡¹' AND `is_delete`=0); |
| | | INSERT INTO `project_management_plan_node` (`project_management_plan_id`,`sort`,`name`,`estimated_duration`,`work_content`,`is_delete`,`create_time`,`update_time`,`create_user_name`) |
| | | SELECT @plan_id,2,'å¢éåå·¥',1,'ç¡®å®é¡¹ç®æåä¸å·¥ä½åå·¥',0,NOW(),NOW(),'åçº§èæ¬' WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='å¢éåå·¥' AND `is_delete`=0); |
| | | INSERT INTO `project_management_plan_node` (`project_management_plan_id`,`sort`,`name`,`estimated_duration`,`work_content`,`is_delete`,`create_time`,`update_time`,`create_user_name`) |
| | | SELECT @plan_id,3,'èµææ±æ¥',1,'æ¶éãè¡¥å
å¹¶æ±æ»ç³æ¥èµæ',0,NOW(),NOW(),'åçº§èæ¬' WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='èµææ±æ¥' AND `is_delete`=0); |
| | | INSERT INTO `project_management_plan_node` (`project_management_plan_id`,`sort`,`name`,`estimated_duration`,`work_content`,`is_delete`,`create_time`,`update_time`,`create_user_name`) |
| | | SELECT @plan_id,4,'ç³æ¥',1,'宿æ£å¼ç³æ¥å¹¶ç»è®°ç³æ¥ä¿¡æ¯',0,NOW(),NOW(),'åçº§èæ¬' WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='ç³æ¥' AND `is_delete`=0); |
| | | INSERT INTO `project_management_plan_node` (`project_management_plan_id`,`sort`,`name`,`estimated_duration`,`work_content`,`is_delete`,`create_time`,`update_time`,`create_user_name`) |
| | | SELECT @plan_id,5,'ç³æ¥ç»æ',1,'ç»è®°ç³æ¥ç»æå¹¶å½¢æå®¢æ·å叿æ',0,NOW(),NOW(),'åçº§èæ¬' WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='ç³æ¥ç»æ' AND `is_delete`=0); |
| | | INSERT INTO `project_management_plan_node` (`project_management_plan_id`,`sort`,`name`,`estimated_duration`,`work_content`,`is_delete`,`create_time`,`update_time`,`create_user_name`) |
| | | SELECT @plan_id,6,'éªæ¶',1,'å®æäº¤ä»ãéªæ¶å项ç®å½æ¡£',0,NOW(),NOW(),'åçº§èæ¬' WHERE NOT EXISTS (SELECT 1 FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `name`='éªæ¶' AND `is_delete`=0); |
| | | |
| | | -- 客æ·é¨æ·èåã |
| | | INSERT INTO `sys_menu` (`menu_name`,`parent_id`,`order_num`,`path`,`component`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | SELECT 'å®¢æ·æå¡',0,7,'customerPortal',NULL,'CustomerPortal',1,0,'M','0','0','customer:portal','#','admin',NOW(),'å®¢æ·ææä¸ç»æé¨æ·' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `path`='customerPortal' AND `parent_id`=0); |
| | | SET @portal_menu_id = (SELECT `menu_id` FROM `sys_menu` WHERE `path`='customerPortal' AND `parent_id`=0 ORDER BY `menu_id` LIMIT 1); |
| | | |
| | | INSERT INTO `sys_menu` (`menu_name`,`parent_id`,`order_num`,`path`,`component`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | SELECT 'æçç³æ¥',@portal_menu_id,1,'mySubmission','customerPortal/mySubmission/index','MySubmission',1,0,'C','0','0','customer:submission:mine','upload','admin',NOW(),'客æ·ä¸ä¼ è¿ä¸å¹´ææå¹¶æ¥çç»æ' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id`=@portal_menu_id AND `path`='mySubmission'); |
| | | INSERT INTO `sys_menu` (`menu_name`,`parent_id`,`order_num`,`path`,`component`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | SELECT 'ç³æ¥ç®¡ç',@portal_menu_id,2,'submissionManagement','customerPortal/submissionManagement/index','SubmissionManagement',1,0,'C','0','0','customer:submission:manage','list','admin',NOW(),'å
é¨äººåå®¡æ ¸å¹¶åå¸å®¢æ·ç³æ¥ç»æ' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `parent_id`=@portal_menu_id AND `path`='submissionManagement'); |
| | | SET @my_submission_menu_id = (SELECT `menu_id` FROM `sys_menu` WHERE `parent_id`=@portal_menu_id AND `path`='mySubmission' ORDER BY `menu_id` LIMIT 1); |
| | | |
| | | -- éå®å°è´¦ä¸çä¸å¡å®¡æ¹æé®æéãå·²æéå®å°è´¦æéçè§è²èªå¨è·å¾âæäº¤âï¼ |
| | | -- â审æ¹âä¸èªå¨æäºï¼ç±ç®¡çåå¨ç°æè§è²ç®¡çä¸åé
ç»ç®¡ç人åã |
| | | SET @sales_ledger_menu_id = (SELECT `menu_id` FROM `sys_menu` WHERE `component`='salesManagement/salesLedger/index' ORDER BY `menu_id` LIMIT 1); |
| | | INSERT INTO `sys_menu` (`menu_name`,`parent_id`,`order_num`,`path`,`component`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | SELECT 'æäº¤ä¸å¡å®¡æ¹',@sales_ledger_menu_id,20,'#',NULL,'',1,0,'F','0','0','business:approval:submit','#','admin',NOW(),'æäº¤ä¸å¡è¿å
¥ç®¡ç审æ¹' |
| | | WHERE @sales_ledger_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms`='business:approval:submit'); |
| | | INSERT INTO `sys_menu` (`menu_name`,`parent_id`,`order_num`,`path`,`component`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | SELECT 'å®¡æ ¸ä¸å¡',@sales_ledger_menu_id,21,'#',NULL,'',1,0,'F','0','0','business:approval:review','#','admin',NOW(),'审æ¹éè¿åèªå¨çæé¡¹ç®' |
| | | WHERE @sales_ledger_menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms`='business:approval:review'); |
| | | SET @business_submit_menu_id = (SELECT `menu_id` FROM `sys_menu` WHERE `perms`='business:approval:submit' ORDER BY `menu_id` LIMIT 1); |
| | | INSERT IGNORE INTO `sys_role_menu` (`role_id`,`menu_id`) |
| | | SELECT `role_id`, @business_submit_menu_id FROM `sys_role_menu` |
| | | WHERE `menu_id`=@sales_ledger_menu_id AND @business_submit_menu_id IS NOT NULL; |
| | | |
| | | -- 客æ·è§è²åªæäºå®¢æ·é¨æ·åâæçç³æ¥âï¼è´¦å·ä»ç±ç®¡çåå¨ç°æç¨æ·ç®¡çä¸å建ã |
| | | INSERT INTO `sys_role` (`role_name`,`role_key`,`role_sort`,`data_scope`,`menu_check_strictly`,`dept_check_strictly`,`status`,`del_flag`,`create_by`,`create_time`,`remark`) |
| | | SELECT '客æ·','customer',90,'5',1,1,'0','0','admin',NOW(),'å¤é¨å®¢æ·è´¦å·ï¼ä¸å¼æ¾èªè¡æ³¨å' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `sys_role` WHERE `role_key`='customer' AND `del_flag`='0'); |
| | | SET @customer_role_id = (SELECT `role_id` FROM `sys_role` WHERE `role_key`='customer' AND `del_flag`='0' ORDER BY `role_id` LIMIT 1); |
| | | INSERT IGNORE INTO `sys_role_menu` (`role_id`,`menu_id`) VALUES (@customer_role_id,@portal_menu_id),(@customer_role_id,@my_submission_menu_id); |
| | | |
| | | SET FOREIGN_KEY_CHECKS = 1; |
| | | |
| | | -- æ§è¡åæ£æ¥ |
| | | SELECT 'customer_submission' AS item, COUNT(*) AS count_value FROM `customer_submission` |
| | | UNION ALL SELECT 'six_stage_nodes', COUNT(*) FROM `project_management_plan_node` WHERE `project_management_plan_id`=@plan_id AND `is_delete`=0 |
| | | UNION ALL SELECT 'customer_portal_menus', COUNT(*) FROM `sys_menu` WHERE `menu_id`=@portal_menu_id OR `parent_id`=@portal_menu_id; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- å®¢æ·æ¡£æ¡æé®æéè¡¥å
ï¼å
¬æµ·å®¢æ·é¡µé¢åæ ·ä½¿ç¨å®¢æ·æ°å¢/ä¿®æ¹/å 餿¥å£ |
| | | INSERT IGNORE INTO `sys_role_menu` (`role_id`,`menu_id`) |
| | | SELECT rm.role_id, m.menu_id FROM `sys_role_menu` rm |
| | | JOIN `sys_menu` m ON m.menu_id BETWEEN 2362 AND 2368 |
| | | WHERE rm.menu_id = 2290; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 客æ·è¿3å¹´è´¢å¡æ¥è¡¨ + å®¢æ·æ¡£æ¡æé®æé |
| | | -- æ§è¡åºï¼product-inventory-management-xmgl |
| | | |
| | | CREATE TABLE IF NOT EXISTS `customer_financial_report` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主é®', |
| | | `customer_id` bigint NOT NULL COMMENT '客æ·id', |
| | | `report_year` int NOT NULL COMMENT 'æ¥è¡¨å¹´åº¦', |
| | | `report_name` varchar(255) DEFAULT NULL COMMENT 'æ¥è¡¨åç§°', |
| | | `report_files` text COMMENT 'æ¥è¡¨éä»¶(JSONæ°ç»)', |
| | | `upload_user_id` bigint DEFAULT NULL COMMENT 'ä¸ä¼ 人id', |
| | | `upload_user_name` varchar(100) DEFAULT NULL COMMENT 'ä¸ä¼ 人', |
| | | `tenant_id` bigint DEFAULT NULL COMMENT 'ç§æ·id', |
| | | `dept_id` bigint DEFAULT NULL COMMENT 'é¨é¨id', |
| | | `create_time` datetime DEFAULT NULL COMMENT 'å建æ¶é´', |
| | | `update_time` datetime DEFAULT NULL COMMENT 'æ´æ°æ¶é´', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_customer_year` (`customer_id`, `report_year`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客æ·è¿3å¹´è´¢å¡æ¥è¡¨'; |
| | | |
| | | -- å®¢æ·æ¡£æ¡èåæä¸æ¥è¯¢æé |
| | | UPDATE `sys_menu` SET `perms` = 'basic:customer:list' WHERE `menu_id` = 2001; |
| | | |
| | | -- æé®æéï¼F ç±»åï¼ |
| | | INSERT IGNORE INTO `sys_menu` (`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,`query`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | VALUES |
| | | (2362,'å®¢æ·æ°å¢',2001,2,'',NULL,'','',1,0,'F','0','0','basic:customer:add','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2363,'客æ·ä¿®æ¹',2001,3,'',NULL,'','',1,0,'F','0','0','basic:customer:edit','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2364,'客æ·å é¤',2001,4,'',NULL,'','',1,0,'F','0','0','basic:customer:remove','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2365,'客æ·å¯¼å
¥',2001,5,'',NULL,'','',1,0,'F','0','0','basic:customer:import','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2366,'客æ·å¯¼åº',2001,6,'',NULL,'','',1,0,'F','0','0','basic:customer:export','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2367,'客æ·è¯¦æ
',2001,7,'',NULL,'','',1,0,'F','0','0','basic:customer:query','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'), |
| | | (2368,'è¿3å¹´è´¢å¡æ¥è¡¨ä¸ä¼ ',2001,8,'',NULL,'','',1,0,'F','0','0','basic:customer:report','#','admin',NOW(),'å®¢æ·æ¡£æ¡æé®æé'); |
| | | |
| | | -- å·²æå®¢æ·æ¡£æ¡èåçè§è²èªå¨è·å¾æé®æé |
| | | INSERT IGNORE INTO `sys_role_menu` (`role_id`,`menu_id`) |
| | | SELECT rm.role_id, m.menu_id FROM `sys_role_menu` rm |
| | | JOIN `sys_menu` m ON m.menu_id BETWEEN 2362 AND 2368 |
| | | WHERE rm.menu_id = 2001; |
| | | |
| | | -- 客æ·é¨æ·ï¼æçç³æ¥ -> è´¢å¡æ¥è¡¨ä¸ä¼ ï¼ä»
客æ·è§è²å¯è§ï¼ |
| | | UPDATE `sys_menu` SET `menu_name` = 'è´¢å¡æ¥è¡¨ä¸ä¼ ', `path` = 'myReport', `route_name` = 'MyReport', |
| | | `component` = 'customerPortal/myReport/index', `perms` = 'customer:report:mine', `order_num` = 2 |
| | | WHERE `menu_id` = 2358; |
| | | |
| | | -- å·²ç»å®ç客æ·è´¦å·è¡¥æâ客æ·âè§è²ï¼role_key=customerï¼ |
| | | INSERT IGNORE INTO `sys_user_role` (`user_id`,`role_id`) |
| | | SELECT DISTINCT cu.user_id, r.role_id FROM `customer_user` cu |
| | | JOIN `sys_role` r ON r.role_key = 'customer' AND r.del_flag = '0'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 客æ·è¿3å¹´è´¢å¡æ¥è¡¨ï¼å®¡æ¹ç¶æ + è¥é管çä¸çâç³æ¥å®¡æ¹âèå |
| | | -- æ§è¡åºï¼product-inventory-management-xmgl |
| | | |
| | | ALTER TABLE `customer_financial_report` |
| | | ADD COLUMN `audit_status` tinyint NOT NULL DEFAULT 0 COMMENT '审æ¹ç¶æï¼0å¾
å®¡æ¹ 1å·²éè¿ 2å·²éå' AFTER `report_files`, |
| | | ADD COLUMN `audit_user_id` bigint DEFAULT NULL COMMENT '审æ¹äººid' AFTER `audit_status`, |
| | | ADD COLUMN `audit_user_name` varchar(100) DEFAULT NULL COMMENT '审æ¹äºº' AFTER `audit_user_id`, |
| | | ADD COLUMN `audit_time` datetime DEFAULT NULL COMMENT 'å®¡æ¹æ¶é´' AFTER `audit_user_name`, |
| | | ADD COLUMN `audit_remark` varchar(500) DEFAULT NULL COMMENT 'å®¡æ¹æè§/éååå ' AFTER `audit_time`; |
| | | |
| | | -- è¥é管çï¼menu_id=2010ï¼ä¸æ°å¢âç³æ¥å®¡æ¹âèå |
| | | INSERT IGNORE INTO `sys_menu` (`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,`query`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`status`,`perms`,`icon`,`create_by`,`create_time`,`remark`) |
| | | VALUES (2369,'ç³æ¥å®¡æ¹',2010,7,'reportAudit','salesManagement/reportAudit/index','','ReportAudit',1,0,'C','0','0','sales:report:audit','form','admin',NOW(),'客æ·è´¢å¡æ¥è¡¨ç³æ¥å®¡æ¹'); |
| | | |
| | | -- æ¥æè¥é管çèåçè§è²èªå¨è·å¾ç³æ¥å®¡æ¹èå |
| | | INSERT IGNORE INTO `sys_role_menu` (`role_id`,`menu_id`) |
| | | SELECT rm.role_id, 2369 FROM `sys_role_menu` rm WHERE rm.menu_id = 2010; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ç²¾ç®é¡¹ç®èåï¼ä»
åç¨/éèï¼ä¸å é¤èååæéæ°æ®ã |
| | | -- ä¿çï¼ç³»ç»ç®¡çãå®¢æ·æå¡ãè¥éä¸å¡ãéè´å°è´¦ã项ç®ç®¡çã请å/审æ¹ã |
| | | SET NAMES utf8mb4; |
| | | |
| | | START TRANSACTION; |
| | | |
| | | -- åç¨å½å项ç®ä¸ä½¿ç¨çä¸çº§ä¸å¡ç®å½ã |
| | | UPDATE sys_menu |
| | | SET visible = '1', status = '1', update_time = NOW(), update_by = 'admin' |
| | | WHERE menu_id IN ( |
| | | 2000, -- åºç¡æ°æ® |
| | | 2291, -- å·¥èºè®¾è®¡ |
| | | 2029, -- ä»å¨ |
| | | 2033, -- 人åèµæºï¼äººåãé¨é¨æ¹ç¨ç³»ç»ç®¡çï¼ |
| | | 2058, -- çäº§ç®¡æ§ |
| | | 2071, -- è´¢å¡ç®¡ç |
| | | 2101, -- æ¡£æ¡ç®¡ç |
| | | 2040, -- è´¨é管ç |
| | | 2042, -- 设å¤ç®¡ç |
| | | 2064, -- è½è管ç |
| | | 2036, -- 管çé©¾é©¶è± |
| | | 2190, -- å³ä¿ç®¡ç |
| | | 2067, -- å®åæå¡ |
| | | 2250 -- å®å
¨ç产 |
| | | ); |
| | | |
| | | -- è¥é管çåªä¿çå®¢æ·æ¡£æ¡(ç§æµ·)ãéå®å°è´¦åå
¶æé®æéã |
| | | UPDATE sys_menu |
| | | SET visible = '1', status = '1', update_time = NOW(), update_by = 'admin' |
| | | WHERE parent_id = 2010 |
| | | AND menu_id NOT IN (2001, 2011) |
| | | AND menu_type <> 'F'; |
| | | |
| | | -- éè´ç®¡çä¿çä¾åºåæ¡£æ¡ãéè´å°è´¦ã |
| | | UPDATE sys_menu |
| | | SET visible = '1', status = '1', update_time = NOW(), update_by = 'admin' |
| | | WHERE parent_id = 2015 |
| | | AND menu_id NOT IN (2012, 2016) |
| | | AND menu_type <> 'F'; |
| | | |
| | | -- åååå
¬ä¿ç审æ¹ç®¡çãåå审æ¹ãèå¤ç®¡çã |
| | | UPDATE sys_menu |
| | | SET visible = '1', status = '1', update_time = NOW(), update_by = 'admin' |
| | | WHERE parent_id = 2053 |
| | | AND menu_id NOT IN (2327, 2326, 2306) |
| | | AND menu_type <> 'F'; |
| | | |
| | | -- èå¤ç®¡çåªä¿ç请åç³è¯·ï¼åºå·®ç审æ¹ç»ä¸ä»åå审æ¹è¿å
¥ã |
| | | UPDATE sys_menu |
| | | SET visible = '1', status = '1', update_time = NOW(), update_by = 'admin' |
| | | WHERE parent_id = 2306 |
| | | AND menu_id <> 2320 |
| | | AND menu_type <> 'F'; |
| | | |
| | | -- æç¡®å¯ç¨éè¦ä¿ççä¸çº§èååæ ¸å¿ä¸å¡èåã |
| | | UPDATE sys_menu |
| | | SET visible = '0', status = '0', update_time = NOW(), update_by = 'admin' |
| | | WHERE menu_id IN ( |
| | | 1, -- ç³»ç»ç®¡ç |
| | | 2053, -- åååå
¬ |
| | | 2010, -- è¥é管ç |
| | | 2015, -- éè´ç®¡ç |
| | | 2276, -- 项ç®ç®¡ç |
| | | 2357, -- å®¢æ·æå¡ |
| | | 100,101,102,103,104,105,108,2287, |
| | | 2327,2326,2306,2320, |
| | | 2001,2011, |
| | | 2012,2016, |
| | | 2277,2283,2285, |
| | | 2358,2359 |
| | | ); |
| | | |
| | | -- å°ææå·²åç¨/éèèåçä¸çº§è·¯ç±åæé®æé忥åç¨ã |
| | | -- èåæ å½åå±çº§æéï¼éå±åæ¥ 8 次足以è¦çå
¨é¨å代èç¹ã |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | UPDATE sys_menu child JOIN sys_menu parent ON child.parent_id = parent.menu_id SET child.visible = '1', child.status = '1', child.update_time = NOW(), child.update_by = 'admin' WHERE parent.status = '1' OR parent.visible = '1'; |
| | | |
| | | COMMIT; |
| | | |
| | | -- æ£æ¥æç»æ¾ç¤ºçèåè·¯ç±ã |
| | | SELECT menu_id, menu_name, parent_id, path, component, menu_type |
| | | FROM sys_menu |
| | | WHERE visible = '0' AND status = '0' AND menu_type IN ('M','C') |
| | | ORDER BY parent_id, order_num, menu_id; |
| | | |
| | | -- å¦éæ¢å¤ï¼å¯ä»è忏
çåçå
¨åºå¤ä»½æ¢å¤ï¼æ¬èæ¬æ²¡æå é¤ä»»ä½è®°å½ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 项ç®ç®¡çï¼æ°å¢â项ç®åç±»âåæ®µï¼å
åæ»å 个é项ï¼åç»å¯åæ¢ä¸ºåå
¸ï¼ |
| | | ALTER TABLE project_management_info |
| | | ADD COLUMN project_classification varchar(50) DEFAULT NULL COMMENT '项ç®åç±»' AFTER source; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- éè´å°è´¦ï¼ç®ï¼è¡¨ï¼ä»
ä¿çåºç¡å段ï¼ä¸ä¸åå/ä¾åºå/产å/å
¥åºç模åå
³è |
| | | CREATE TABLE IF NOT EXISTS purchase_ledger_simple ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主é®ID', |
| | | payment_date DATE DEFAULT NULL COMMENT 'æ¯åºæ¥æ', |
| | | purchase_date DATE DEFAULT NULL COMMENT 'éè´æ¥æ', |
| | | amount DECIMAL(18, 2) DEFAULT NULL COMMENT 'éé¢', |
| | | purchase_goods VARCHAR(255) DEFAULT NULL COMMENT 'éè´ç©å', |
| | | invoice_number VARCHAR(100) DEFAULT NULL COMMENT 'å票å·ç ', |
| | | payment_method VARCHAR(50) DEFAULT NULL COMMENT '仿¬¾æ¹å¼', |
| | | remark VARCHAR(500) DEFAULT NULL COMMENT '夿³¨', |
| | | recorder_id BIGINT DEFAULT NULL COMMENT 'å½å
¥äººid', |
| | | recorder_name VARCHAR(64) DEFAULT NULL COMMENT 'å½å
¥äºº', |
| | | entry_date DATE DEFAULT NULL COMMENT 'å½å
¥æ¥æ', |
| | | create_user BIGINT DEFAULT NULL COMMENT 'åå»ºç¨æ·', |
| | | dept_id BIGINT DEFAULT NULL COMMENT 'é¨é¨id', |
| | | create_time DATETIME DEFAULT NULL COMMENT 'å建æ¶é´', |
| | | update_time DATETIME DEFAULT NULL COMMENT 'æ´æ°æ¶é´', |
| | | PRIMARY KEY (id) |
| | | ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'éè´å°è´¦ï¼ç®ï¼'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ°æ®åºå级说æ |
| | | |
| | | 1. å·²å¨å¼ååå®ææ°æ®åºå
¨éå¤ä»½ï¼`D:\newcode\product-inventory-management\.local-backups\product-inventory-management-xmgl-20261028-131516.sql`ï¼çº¦ 83.5 MBï¼ã |
| | | 2. æ£å¼æ§è¡åï¼å»ºè®®åçæä¸ä»½å½æ¶ç¹å¤ä»½ï¼å¹¶ç¡®è®¤å¤ä»½æä»¶å¤§å°ä¸æ¯ 0ã |
| | | 3. ä½¿ç¨æ°æ®åºç®¡çå·¥å
·éæ© `product-inventory-management-xmgl`ï¼æ§è¡ `20261028_customer_project_upgrade.sql`ã |
| | | 4. èæ¬åªæ°å¢ä¸å¡è¡¨ãéå®å°è´¦å段ãå
é¶æ®µæ¨¡æ¿ã客æ·è§è²åèåï¼ä¸å é¤åæ°æ®ï¼å䏿°æ®åºå¯é夿§è¡ã |
| | | 5. æ§è¡å®æåï¼æ«å°¾æ£æ¥ç»æåºä¸ºï¼å
é¶æ®µèç¹æ°é `6`ï¼å®¢æ·æå¡èåæ°é `3`ã |
| | | 6. 管çåå¨ç°æâç¨æ·ç®¡çâå建客æ·è´¦å·ï¼èµäºâ客æ·âè§è²ï¼åå¨ç°æâå®¢æ·æ¡£æ¡âä¸æè¯¥ç¨æ·ç»å®å°å¯¹åºå®¢æ·ãå
é¨åå·¥èåæéä»å¨ç°æè§è²ç®¡çä¸é
ç½®ã |
| | | |
| | | åæ»æ¹å¼ï¼åæ¢åºç¨ï¼å建ä¸ä¸ªç©ºåºå导å
¥ä¸è¿°å
¨éå¤ä»½ï¼ç¡®è®¤æ 误ååæ¢è¿æ¥ãä¸è¦å¨ææ°ä¸å¡æ°æ®äº§çåç´æ¥è¦çç产åºã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // åå°ï¼æå®¢æ·æ¥è¯¢è¿3å¹´è´¢å¡æ¥è¡¨ |
| | | export function listCustomerFinancialReport(params) { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/list', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // 客æ·è´¦å·ï¼æ¥è¯¢èªå·±çè¿3å¹´è´¢å¡æ¥è¡¨ |
| | | export function listMyFinancialReport() { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/mine', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // ä¸ä¼ /ä¿®æ¹è´¢å¡æ¥è¡¨ |
| | | export function saveCustomerFinancialReport(data) { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/save', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // å é¤è´¢å¡æ¥è¡¨ï¼ä»
åå°äººåï¼ |
| | | export function delCustomerFinancialReport(id) { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/' + id, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | // ç³æ¥å®¡æ¹ï¼æ¥è¯¢ææå®¢æ·æäº¤æ
åµ |
| | | export function auditListCustomerFinancialReport(params) { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/auditList', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // ç³æ¥å®¡æ¹ï¼éè¿/éå |
| | | export function auditCustomerFinancialReport(data) { |
| | | return request({ |
| | | url: '/basic/customerFinancialReport/audit', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function listMySubmissions(params) { |
| | | return request({ url: "/customer/submission/mine", method: "get", params }); |
| | | } |
| | | |
| | | export function submitCustomerMaterial(data) { |
| | | return request({ url: "/customer/submission", method: "post", data }); |
| | | } |
| | | |
| | | export function listSubmissionManage(params) { |
| | | return request({ url: "/customer/submission/manage/list", method: "get", params }); |
| | | } |
| | | |
| | | export function reviewSubmission(data) { |
| | | return request({ url: "/customer/submission/review", method: "put", data }); |
| | | } |
| | |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ===== éè´å°è´¦ï¼ç®ï¼ï¼ç¬ç«ç®åå°è´¦ï¼ä¸ä¸åå/ä¾åºå/产å/å
¥åºå
³è ===== |
| | | export function simpleLedgerList(params) { |
| | | return request({ |
| | | url: "/purchase/simpleLedger/list", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | export function simpleLedgerGet(id) { |
| | | return request({ |
| | | url: `/purchase/simpleLedger/${id}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function simpleLedgerSave(data) { |
| | | return request({ |
| | | url: "/purchase/simpleLedger/save", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | export function simpleLedgerDelete(ids) { |
| | | return request({ |
| | | url: "/purchase/simpleLedger/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | // ä¸å¡æäº¤ç®¡çå®¡æ¹ |
| | | export function submitBusinessApproval(id) { |
| | | return request({ |
| | | url: `/business/approval/submit/${id}`, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // 管ç人åå®¡æ ¸ä¸å¡ï¼éè¿åå端èªå¨çæå
é¶æ®µé¡¹ç® |
| | | export function reviewBusinessApproval(data) { |
| | | return request({ |
| | | url: "/business/approval/review", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // æ ¹æ®éå®ååå·æ¥äº§åä¿¡æ¯ |
| | | export function getProductInfoByContractNo(query) { |
| | | return request({ |
| | |
| | | <template>
|
| | | <div class="upload-file">
|
| | | <el-upload multiple :action="uploadFileUrl" :before-upload="handleBeforeUpload" :file-list="fileList" :data="data"
|
| | | <el-upload multiple :action="uploadFileUrl" name="files" :before-upload="handleBeforeUpload" :file-list="fileList" :data="data"
|
| | | :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" :on-success="handleUploadSuccess"
|
| | | :show-file-list="false" :headers="headers" class="upload-file-uploader" ref="fileUpload" v-if="!disabled">
|
| | | <!-- ä¸ä¼ æé® -->
|
| | |
| | | çæä»¶
|
| | | </div>
|
| | | <!-- æä»¶å表 -->
|
| | | <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
|
| | | <transition-group ref="uploadListRef" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> |
| | | <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
|
| | | <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
|
| | | <el-link :href="getFileUrl(file.url)" :underline="false" target="_blank"> |
| | | <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
|
| | | </el-link>
|
| | | <div class="ele-upload-list__item-content-action">
|
| | |
| | | const { proxy } = getCurrentInstance();
|
| | | const emit = defineEmits();
|
| | | const number = ref(0);
|
| | | const uploadList = ref([]);
|
| | | const uploadList = ref([]); |
| | | const uploadListRef = ref(); |
| | | const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
| | | const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action); // ä¸ä¼ æä»¶æå¡å¨å°å
|
| | | const headers = ref({ Authorization: "Bearer " + getToken() });
|
| | |
| | | (val) => {
|
| | | if (val) {
|
| | | let temp = 1;
|
| | | // é¦å
å°å¼è½¬ä¸ºæ°ç»
|
| | | const list = Array.isArray(val) ? val : props.modelValue.split(",");
|
| | | let list = [];
|
| | | if (Array.isArray(val)) {
|
| | | list = val;
|
| | | } else if (typeof val === "string" && val.trim().startsWith("[")) {
|
| | | // å端è¿å/å端ä¿åç JSON æ°ç»æ ¼å¼ï¼[{ id, name, url }]
|
| | | try {
|
| | | const parsed = JSON.parse(val);
|
| | | list = Array.isArray(parsed) ? parsed : [];
|
| | | } catch (e) {
|
| | | list = val.split(",");
|
| | | }
|
| | | } else {
|
| | | list = String(val).split(",");
|
| | | }
|
| | | // ç¶åå°æ°ç»è½¬ä¸ºå¯¹è±¡æ°ç»
|
| | | fileList.value = list.map((item) => {
|
| | | if (typeof item === "string") {
|
| | |
| | |
|
| | | // ä¸ä¼ æååè°
|
| | | function handleUploadSuccess(res, file) {
|
| | | if (res.code === 200) {
|
| | | uploadList.value.push({ name: res.fileName, url: res.fileName });
|
| | | // å端 /common/upload è¿å { code, msg, data: [ { id, name, url, downloadURL ... } ] }
|
| | | const list = Array.isArray(res?.data) ? res.data : [];
|
| | | if (res?.code === 200 && list.length > 0) {
|
| | | list.forEach((item) => {
|
| | | uploadList.value.push({
|
| | | id: item.id ?? item.storageAttachmentId,
|
| | | name: item.name || item.originalFilename || item.fileName || file.name,
|
| | | url: item.url || item.downloadURL || item.previewURL || item.previewUrl || "",
|
| | | });
|
| | | });
|
| | | uploadedSuccessfully();
|
| | | } else {
|
| | | number.value--;
|
| | | proxy.$modal.closeLoading();
|
| | | proxy.$modal.msgError(res.msg);
|
| | | proxy.$modal.msgError(res?.msg || "ä¸ä¼ æä»¶å¤±è´¥");
|
| | | proxy.$refs.fileUpload.handleRemove(file);
|
| | | uploadedSuccessfully();
|
| | | }
|
| | |
| | | }
|
| | |
|
| | | // è·åæä»¶åç§°
|
| | | function getFileName(name) {
|
| | | function getFileName(name) { |
| | | // 妿æ¯urlé£ä¹åæåçåå 妿䏿¯ç´æ¥è¿å
|
| | | if (name.lastIndexOf("/") > -1) {
|
| | | return name.slice(name.lastIndexOf("/") + 1);
|
| | | } else {
|
| | | return name;
|
| | | }
|
| | | }
|
| | | } |
| | | |
| | | function getFileUrl(url) { |
| | | if (!url) return ""; |
| | | return /^https?:\/\//i.test(url) ? url : `${baseUrl}${url}`; |
| | | } |
| | |
|
| | | // 对象转ææå®å符串åé
|
| | | function listToString(list, separator) {
|
| | | let strs = "";
|
| | | separator = separator || ",";
|
| | | for (let i in list) {
|
| | | if (list[i].url) {
|
| | | strs += list[i].url + separator;
|
| | | }
|
| | | }
|
| | | return strs != "" ? strs.substr(0, strs.length - 1) : "";
|
| | | // 对象转æ JSON æ°ç»å符串ï¼ä¿åæä»¶ idãåç§°ä¸å°å
|
| | | function listToString(list) {
|
| | | const files = (list || [])
|
| | | .filter((item) => item && item.url)
|
| | | .map((item) => ({ id: item.id, name: item.name, url: item.url }));
|
| | | return files.length > 0 ? JSON.stringify(files) : "";
|
| | | }
|
| | |
|
| | | // åå§åææ½æåº
|
| | | onMounted(() => {
|
| | | if (props.drag) {
|
| | | nextTick(() => {
|
| | | const element = document.querySelector(".upload-file-list");
|
| | | Sortable.create(element, {
|
| | | onMounted(() => { |
| | | if (props.drag && !props.disabled) { |
| | | nextTick(() => { |
| | | const element = uploadListRef.value?.$el || uploadListRef.value; |
| | | if (!element) return; |
| | | Sortable.create(element, { |
| | | ghostClass: "file-upload-darg",
|
| | | onEnd: (evt) => {
|
| | | const movedItem = fileList.value.splice(evt.oldIndex, 1)[0];
|
| | |
| | | <template> |
| | | <el-dialog v-model="visible" title="è¿åº¦æ±æ¥" width="900px" top="8vh" append-to-body destroy-on-close @close="handleClose"> |
| | | <el-dialog v-model="visible" title="è¿åº¦æ±æ¥" width="960px" top="6vh" append-to-body destroy-on-close @close="handleClose"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-position="top" label-width="120px"> |
| | | <el-form-item label="项ç®é¶æ®µ" prop="planNodeId"> |
| | | <el-select v-model="form.planNodeId" placeholder="è¯·éæ©" clearable style="width: 100%"> |
| | | <el-select v-model="form.planNodeId" placeholder="è¯·éæ©" style="width: 100%" @change="handleStageChange"> |
| | | <el-option v-for="opt in stageOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> |
| | | </el-select> |
| | | <div v-if="stageTip" class="stage-tip">{{ stageTip }}</div> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="䏿¬¡è¿åº¦(%)" prop="lastProgress"> |
| | | <el-input-number v-model="form.lastProgress" :min="0" :max="100" controls-position="right" style="width: 100%" /> |
| | | <el-form-item label="䏿¬¡è¿åº¦(%)"> |
| | | <el-input-number v-model="form.lastProgress" :min="0" :max="100" controls-position="right" style="width: 100%" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <div v-if="isTeamStage" class="team-block"> |
| | | <div class="team-header"> |
| | | <span class="team-title">项ç®å¢é</span> |
| | | <el-button type="primary" size="small" @click="addTeamRow">æ°å¢æå</el-button> |
| | | </div> |
| | | <el-table :data="teamRows" border size="small"> |
| | | <el-table-column label="å§å" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-select v-model="row.userId" placeholder="è¯·éæ©" filterable clearable style="width: 100%"> |
| | | <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="项ç®ç»è§è²" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-select v-model="row.userRoleId" placeholder="è¯·éæ©" filterable clearable style="width: 100%"> |
| | | <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è¿å
¥æ¥æ" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-date-picker |
| | | v-model="row.joinTime" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¦»å¼æ¥æ" width="150"> |
| | | <template #default="{ row }"> |
| | | <el-date-picker |
| | | v-model="row.departTime" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="èç³»æ¹å¼" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.contact" placeholder="请è¾å
¥" clearable /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" min-width="140"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.remark" placeholder="请è¾å
¥" clearable /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="70" align="center"> |
| | | <template #default="{ $index }"> |
| | | <el-button link type="danger" size="small" @click="removeTeamRow($index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-form-item label="éä»¶" prop="attachmentIds"> |
| | | <FileUpload v-model:file-list="form.storageBlobDTOs" /> |
| | | </el-form-item> |
| | |
| | | </template> |
| | | |
| | | <script setup name="ProgressReportDialog"> |
| | | import { computed, reactive, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getToken } from '@/utils/auth' |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { computed, ref, watch } from 'vue' |
| | | import { ElMessageBox } from 'element-plus' |
| | | import FileUpload from '@/components/AttachmentUpload/file/index.vue' |
| | | import { findRoleListPage } from '@/api/projectManagement/role' |
| | | import { userListAll } from '@/api/publicApi' |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { type: Boolean, default: false }, |
| | | projectId: { type: [Number, String], default: undefined }, |
| | | projectInfo: { type: Object, default: () => ({}) }, |
| | | planNodes: { type: Array, default: () => [] }, |
| | | stageRecords: { type: Array, default: () => [] }, |
| | | defaultPlanNodeId: { type: [Number, String], default: undefined } |
| | | }) |
| | | |
| | |
| | | }) |
| | | |
| | | const formRef = ref() |
| | | const userOptions = ref([]) |
| | | const roleOptions = ref([]) |
| | | const teamRows = ref([]) |
| | | |
| | | const form = ref({ |
| | | planNodeId: undefined, |
| | |
| | | completionProgress: [{ required: true, message: '请è¾å
¥', trigger: 'change' }] |
| | | } |
| | | |
| | | const stageOptions = computed(() => { |
| | | const sortedNodes = computed(() => { |
| | | const list = Array.isArray(props.planNodes) ? props.planNodes : [] |
| | | const sorted = [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | return sorted |
| | | return [...list].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | }) |
| | | |
| | | const stageOptions = computed(() => |
| | | sortedNodes.value |
| | | .map(n => ({ |
| | | label: n.name || n.workContent || n.title || String(n.id ?? ''), |
| | | value: n.id |
| | | })) |
| | | .filter(i => i.value !== undefined && i.value !== null && i.value !== '') |
| | | ) |
| | | |
| | | const stageNameOf = nodeId => { |
| | | const node = sortedNodes.value.find(n => String(n.id) === String(nodeId)) |
| | | return node?.name || node?.workContent || '' |
| | | } |
| | | |
| | | // åé¶æ®µæè¿ä¸æ¡æ±æ¥è®°å½ï¼è®°å½æé¶æ®µãæ¶é´ååºè¿åï¼ |
| | | const latestProgressMap = computed(() => { |
| | | const map = {} |
| | | const records = Array.isArray(props.stageRecords) ? props.stageRecords : [] |
| | | records.forEach(r => { |
| | | const key = String(r.projectManagementPlanNodeId ?? '') |
| | | map[key] = Number(r.progress) || 0 |
| | | }) |
| | | return map |
| | | }) |
| | | |
| | | // å½ååºæ±æ¥çä¸ä¸é¶æ®µï¼ç¬¬ä¸ä¸ªæªå®æï¼è¿åº¦ < 100ï¼çé¶æ®µ |
| | | const expectedNodeId = computed(() => { |
| | | const nodes = sortedNodes.value |
| | | if (nodes.length === 0) return undefined |
| | | const next = nodes.find(n => (latestProgressMap.value[String(n.id)] ?? 0) < 100) |
| | | return (next || nodes[nodes.length - 1])?.id |
| | | }) |
| | | |
| | | const isTeamStage = computed(() => stageNameOf(form.value.planNodeId).includes('å¢é')) |
| | | |
| | | const stageTip = computed(() => { |
| | | if (!form.value.planNodeId) return '' |
| | | const progress = latestProgressMap.value[String(form.value.planNodeId)] ?? 0 |
| | | if (progress >= 100) return 'è¯¥é¶æ®µå·²å®æï¼ä»å¯ç»§ç»è¡¥å
æ±æ¥' |
| | | if (expectedNodeId.value && String(form.value.planNodeId) !== String(expectedNodeId.value)) { |
| | | return 'å½åéæ©ç䏿¯åºæ±æ¥çä¸ä¸é¶æ®µ' |
| | | } |
| | | return '' |
| | | }) |
| | | |
| | | function todayStr() { |
| | | const d = new Date() |
| | | const pad = n => String(n).padStart(2, '0') |
| | | return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` |
| | | } |
| | | |
| | | function latestRecordOf(nodeId) { |
| | | const records = Array.isArray(props.stageRecords) ? props.stageRecords : [] |
| | | const matched = records.filter(r => String(r.projectManagementPlanNodeId) === String(nodeId)) |
| | | return matched.length ? matched[matched.length - 1] : null |
| | | } |
| | | |
| | | function applyStageBase(nodeId) { |
| | | const record = latestRecordOf(nodeId) |
| | | form.value.lastProgress = Number(record?.progress ?? 0) || 0 |
| | | form.value.completionProgress = 0 |
| | | form.value.totalProgress = Number(record?.progress ?? 0) || 0 |
| | | form.value.planStartTime = record?.planStartTime || props.projectInfo?.planStartTime || '' |
| | | form.value.planEndTime = record?.planEndTime || props.projectInfo?.planEndTime || '' |
| | | form.value.actualStartTime = record?.actualStartTime || props.projectInfo?.actualStartTime || '' |
| | | form.value.actualEndTime = record?.actualEndTime || '' |
| | | } |
| | | |
| | | async function handleStageChange(val) { |
| | | applyStageBase(val) |
| | | if (!val) return |
| | | const expected = expectedNodeId.value |
| | | if (expected && String(val) !== String(expected)) { |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `å½ååºæ±æ¥çâ${stageNameOf(expected)}âé¶æ®µè¿æªå®æï¼éæ©å
¶ä»é¶æ®µå°è·³è¿ä¸é´é¶æ®µï¼æ¯å¦ç»§ç»ï¼`, |
| | | 'æç¤º', |
| | | { confirmButtonText: 'ç»§ç»', cancelButtonText: 'è¿å', type: 'warning' } |
| | | ) |
| | | } catch { |
| | | form.value.planNodeId = expected |
| | | applyStageBase(expected) |
| | | } |
| | | } |
| | | } |
| | | |
| | | function resetFromProject() { |
| | | const info = props.projectInfo || {} |
| | | form.value = { |
| | | planNodeId: props.defaultPlanNodeId ?? stageOptions.value[0]?.value, |
| | | planStartTime: info.planStartTime || '', |
| | | planEndTime: info.planEndTime || '', |
| | | actualStartTime: info.actualStartTime || '', |
| | | actualEndTime: info.actualEndTime || '', |
| | | reportDate: '', |
| | | lastProgress: Number(info.lastProgress ?? 0) || 0, |
| | | planNodeId: props.defaultPlanNodeId ?? expectedNodeId.value ?? stageOptions.value[0]?.value, |
| | | planStartTime: '', |
| | | planEndTime: '', |
| | | actualStartTime: '', |
| | | actualEndTime: '', |
| | | reportDate: todayStr(), |
| | | lastProgress: 0, |
| | | completionProgress: 0, |
| | | totalProgress: Number(info.totalProgress ?? info.progress ?? 0) || 0, |
| | | totalProgress: 0, |
| | | managerName: info.managerName || '', |
| | | departmentName: info.departmentName || '', |
| | | remark: '', |
| | | storageBlobDTOs: [] |
| | | } |
| | | applyStageBase(form.value.planNodeId) |
| | | form.value.reportDate = todayStr() |
| | | loadTeamRows() |
| | | } |
| | | |
| | | function loadTeamRows() { |
| | | const list = Array.isArray(props.projectInfo?.teamList) ? props.projectInfo.teamList : [] |
| | | teamRows.value = list.map(t => ({ |
| | | userId: t.userId, |
| | | userRoleId: t.userRoleId, |
| | | joinTime: t.joinTime || '', |
| | | departTime: t.departTime || '', |
| | | contact: t.contact || '', |
| | | remark: t.remark || '' |
| | | })) |
| | | if (teamRows.value.length === 0) addTeamRow() |
| | | } |
| | | |
| | | function addTeamRow() { |
| | | teamRows.value.push({ userId: undefined, userRoleId: undefined, joinTime: '', departTime: '', contact: '', remark: '' }) |
| | | } |
| | | |
| | | function removeTeamRow(index) { |
| | | teamRows.value.splice(index, 1) |
| | | if (teamRows.value.length === 0) addTeamRow() |
| | | } |
| | | |
| | | async function loadOptions() { |
| | | try { |
| | | const res = await userListAll() |
| | | const list = res?.data || res?.rows || res || [] |
| | | userOptions.value = (Array.isArray(list) ? list : []).map(u => ({ |
| | | label: u.nickName || u.userName || u.username || u.name, |
| | | value: u.userId || u.id |
| | | })) |
| | | } catch { |
| | | userOptions.value = [] |
| | | } |
| | | try { |
| | | const res = await findRoleListPage({ pageNum: 1, pageSize: 999 }) |
| | | const records = res?.data?.records || res?.rows || res?.records || [] |
| | | roleOptions.value = records.map(item => ({ label: item.roleName || item.name, value: item.id })) |
| | | } catch { |
| | | roleOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | v => { |
| | | if (v) resetFromProject() |
| | | if (v) { |
| | | loadOptions() |
| | | resetFromProject() |
| | | } |
| | | } |
| | | ) |
| | | |
| | | // 累计è¿åº¦è·éæ¬æ¬¡å®æè¿åº¦ |
| | | watch( |
| | | () => form.value.completionProgress, |
| | | v => { |
| | | form.value.totalProgress = Number(v) || 0 |
| | | } |
| | | ) |
| | | |
| | |
| | | function markDone() { |
| | | form.value.completionProgress = 100 |
| | | form.value.totalProgress = 100 |
| | | if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || '' |
| | | if (!form.value.actualEndTime) form.value.actualEndTime = form.value.reportDate || todayStr() |
| | | } |
| | | |
| | | function buildTeamList() { |
| | | const findLabel = (list, value) => (list || []).find(i => String(i.value) === String(value))?.label || '' |
| | | return teamRows.value |
| | | .filter(r => r.userId !== undefined && r.userId !== null && r.userId !== '') |
| | | .map(r => ({ |
| | | userId: Number(r.userId), |
| | | userName: findLabel(userOptions.value, r.userId), |
| | | userRoleId: r.userRoleId ?? '', |
| | | userRoleName: findLabel(roleOptions.value, r.userRoleId), |
| | | joinTime: r.joinTime || null, |
| | | departTime: r.departTime || null, |
| | | contact: r.contact || '', |
| | | remark: r.remark || '' |
| | | })) |
| | | } |
| | | |
| | | async function submit() { |
| | | await formRef.value?.validate?.() |
| | | emit('submitted', { |
| | | try { |
| | | await formRef.value?.validate?.() |
| | | } catch { |
| | | return |
| | | } |
| | | const payload = { |
| | | projectId: props.projectId, |
| | | ...form.value |
| | | }) |
| | | } |
| | | if (isTeamStage.value) { |
| | | payload.teamList = buildTeamList() |
| | | } |
| | | emit('submitted', payload) |
| | | visible.value = false |
| | | } |
| | | </script> |
| | |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .stage-tip { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | color: #e6a23c; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .team-block { |
| | | margin-bottom: 18px; |
| | | } |
| | | |
| | | .team-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .team-title { |
| | | font-weight: 600; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | |
|
| | | NProgress.configure({ showSpinner: false })
|
| | |
|
| | | const whiteList = ['/login', '/register', '/callbacklccpn','/device-info']
|
| | | const whiteList = ['/login', '/register', '/callbacklccpn','/device-info'] |
| | |
|
| | | const isWhiteList = (path) => {
|
| | | return whiteList.some(pattern => isPathMatch(pattern, path))
|
| | |
| | | ], |
| | | }, |
| | | // ç³»ç»æ¶æå¾ |
| | | { |
| | | path: "/system-architecture", |
| | | component: Layout, |
| | | redirect: "/system-architecture/index", |
| | | children: [ |
| | | { |
| | | path: "index", |
| | | component: () => import("@/views/systemArchitecture/index.vue"), |
| | | name: "SystemArchitecture", |
| | | meta: { title: "ç³»ç»æ¶æå¾", icon: "tree" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/ai-industrial-brain", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "index", |
| | | component: () => import("@/views/aiIndustrialBrain/index.vue"), |
| | | name: "AiIndustrialBrain", |
| | | meta: { title: "AIå·¥ä¸å¤§è", icon: "skill" }, |
| | | }, |
| | | ], |
| | | }, |
| | | // { |
| | | // path: "/system-architecture", |
| | | // component: Layout, |
| | | // redirect: "/system-architecture/index", |
| | | // children: [ |
| | | // { |
| | | // path: "index", |
| | | // component: () => import("@/views/systemArchitecture/index.vue"), |
| | | // name: "SystemArchitecture", |
| | | // meta: { title: "ç³»ç»æ¶æå¾", icon: "tree" }, |
| | | // }, |
| | | // ], |
| | | // }, |
| | | // { |
| | | // path: "/ai-industrial-brain", |
| | | // component: Layout, |
| | | // children: [ |
| | | // { |
| | | // path: "index", |
| | | // component: () => import("@/views/aiIndustrialBrain/index.vue"), |
| | | // name: "AiIndustrialBrain", |
| | | // meta: { title: "AIå·¥ä¸å¤§è", icon: "skill" }, |
| | | // }, |
| | | // ], |
| | | // }, |
| | | // æå¦æ¼ç¤ºæ¨¡å |
| | | { |
| | | path: "/productionManagement/teachingDemo", |
| | |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" |
| | | v-hasPermi="['basic:customer:add']" |
| | | @click="openForm('add')">æ°å¢å®¢æ·</el-button> |
| | | <el-button type="primary" |
| | | plain |
| | | @click="back">æµå
¥å
¬æµ·</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button v-hasPermi="['basic:customer:export']" |
| | | @click="handleOut">导åº</el-button> |
| | | <el-button type="info" |
| | | plain |
| | | icon="Upload" |
| | | v-hasPermi="['basic:customer:import']" |
| | | @click="handleImport">导å
¥</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | v-hasPermi="['basic:customer:remove']" |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | value="ä¸å¤§æ²¹" /> |
| | | <el-option label="å¤é" |
| | | value="å¤é" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»å®ç»å½è´¦å·ï¼" |
| | | prop="bindUserId"> |
| | | <el-select v-model="form.bindUserId" |
| | | placeholder="è¯·éæ©è¯¥å®¢æ·çç»å½è´¦å·" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName + 'ï¼' + item.userName + 'ï¼'" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- å访æéå¯¹è¯æ¡ --> |
| | | <el-dialog title="å访æé" |
| | | v-model="reminderDialogVisible" |
| | | width="500px" |
| | | @close="closeReminderDialog"> |
| | | <el-form :model="reminderForm" |
| | | label-width="100px" |
| | | :rules="reminderRules" |
| | | ref="reminderFormRef"> |
| | | <el-form-item label="客æ·åç§°ï¼"> |
| | | <el-input v-model="reminderForm.customerName" |
| | | disabled /> |
| | | <!-- è¿3å¹´è´¢å¡æ¥è¡¨ä¸ä¼ --> |
| | | <el-dialog title="è¿3å¹´è´¢å¡æ¥è¡¨ä¸ä¼ " |
| | | v-model="reportDialogVisible" |
| | | width="900px" |
| | | @close="closeReportDialog"> |
| | | <div class="report-customer">客æ·ï¼{{ reportCustomer.customerName }}</div> |
| | | <el-form :model="reportForm" |
| | | label-width="90px" |
| | | inline |
| | | class="report-form"> |
| | | <el-form-item label="æ¥è¡¨å¹´åº¦"> |
| | | <el-select v-model="reportForm.reportYear" |
| | | placeholder="è¯·éæ©å¹´åº¦" |
| | | style="width: 150px"> |
| | | <el-option v-for="year in reportYears" |
| | | :key="year" |
| | | :label="year + 'å¹´'" |
| | | :value="year" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æéå¼å
³ï¼"> |
| | | <el-switch v-model="reminderForm.reminderSwitch" /> |
| | | </el-form-item> |
| | | <el-form-item label="æéå
容ï¼" |
| | | prop="reminderContent"> |
| | | <el-input v-model="reminderForm.reminderContent" |
| | | type="textarea" |
| | | :maxlength="100" |
| | | show-word-limit |
| | | placeholder="请è¾å
¥æéå
容" /> |
| | | </el-form-item> |
| | | <el-form-item label="æéæ¶é´ï¼" |
| | | prop="reminderTime"> |
| | | <el-date-picker v-model="reminderForm.reminderTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="è¯·éæ©æéæ¶é´" |
| | | style="width: 100%" /> |
| | | <el-form-item label="æ¥è¡¨åç§°"> |
| | | <el-input v-model="reportForm.reportName" |
| | | placeholder="ä¾å¦ï¼2025å¹´åº¦è´¢å¡æ¥è¡¨" |
| | | style="width: 260px" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <FileUpload v-model="reportForm.reportFiles" |
| | | :limit="5" |
| | | :file-size="20" |
| | | :file-type="['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png', 'zip', 'rar']" /> |
| | | <div class="report-actions"> |
| | | <el-button type="primary" |
| | | :loading="reportSaving" |
| | | @click="saveReport">ä¿åä¸ä¼ </el-button> |
| | | <el-button @click="resetReportForm">éç½®</el-button> |
| | | </div> |
| | | <el-table :data="reportRows" |
| | | border |
| | | size="small" |
| | | style="margin-top: 16px"> |
| | | <el-table-column prop="reportYear" |
| | | label="年度" |
| | | width="90" |
| | | align="center" /> |
| | | <el-table-column prop="reportName" |
| | | label="æ¥è¡¨åç§°" |
| | | min-width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æ¥è¡¨éä»¶" |
| | | min-width="260"> |
| | | <template #default="scope"> |
| | | <FileUpload v-model="scope.row.reportFiles" |
| | | disabled |
| | | :is-show-tip="false" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="uploadUserName" |
| | | label="ä¸ä¼ 人" |
| | | width="110" /> |
| | | <el-table-column prop="updateTime" |
| | | label="ä¸ä¼ æ¶é´" |
| | | width="170" /> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="reportStatusTag(scope.row.auditStatus)"> |
| | | {{ reportStatusText(scope.row.auditStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="140" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editReport(scope.row)">ä¿®æ¹</el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteReport(scope.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div v-if="reportRows.length === 0" |
| | | class="no-record">ææ è´¢å¡æ¥è¡¨ï¼è¯·æå¹´åº¦ä¸ä¼ è¿3å¹´è´¢å¡æ¥è¡¨</div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitReminderForm">确认</el-button> |
| | | <el-button @click="closeReminderDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æ·»å /ä¿®æ¹æ´½è°è¿åº¦å¯¹è¯æ¡ --> |
| | | <el-dialog :title="negotiationForm.editIndex !== undefined ? 'ä¿®æ¹è¿åº¦' : 'æ·»å è¿åº¦'" |
| | | v-model="negotiationDialogVisible" |
| | | width="600px" |
| | | @close="closeNegotiationDialog"> |
| | | <el-form :model="negotiationForm" |
| | | label-width="100px" |
| | | :rules="negotiationRules" |
| | | ref="negotiationFormRef"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿æ¹å¼ï¼" |
| | | prop="followUpMethod"> |
| | | <el-select v-model="negotiationForm.followUpMethod" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%"> |
| | | <el-option label="çµè¯" |
| | | value="çµè¯" /> |
| | | <el-option label="é®ä»¶" |
| | | value="é®ä»¶" /> |
| | | <el-option label="ä¸é¨" |
| | | value="ä¸é¨" /> |
| | | <el-option label="微信" |
| | | value="微信" /> |
| | | <el-option label="å
¶ä»" |
| | | value="å
¶ä»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿ç¨åº¦ï¼" |
| | | prop="followUpLevel"> |
| | | <el-select v-model="negotiationForm.followUpLevel" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%"> |
| | | <el-option label="æ½å¨å®¢æ·" |
| | | value="æ½å¨å®¢æ·" /> |
| | | <el-option label="忬¡æè®¿" |
| | | value="忬¡æè®¿" /> |
| | | <el-option label="夿¬¡æè®¿" |
| | | value="夿¬¡æè®¿" /> |
| | | <el-option label="æå客æ·" |
| | | value="æå客æ·" /> |
| | | <el-option label="å·²ç¾çº¦å®¢æ·" |
| | | value="å·²ç¾çº¦å®¢æ·" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿æ¶é´ï¼" |
| | | prop="followUpTime"> |
| | | <el-date-picker v-model="negotiationForm.followUpTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è·è¿äººï¼"> |
| | | <el-input v-model="negotiationForm.followerUserName" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
容ï¼" |
| | | prop="content"> |
| | | <el-input v-model="negotiationForm.content" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitNegotiationForm">确认</el-button> |
| | | <el-button @click="closeNegotiationDialog">åæ¶</el-button> |
| | | <el-button @click="closeReportDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | v-model="detailDialogVisible" |
| | | width="1000px" |
| | | @close="closeDetailDialog"> |
| | | <!-- 客æ·åºæ¬ä¿¡æ¯ --> |
| | | <div class="detail-section"> |
| | | <h3 class="section-title">客æ·åºæ¬ä¿¡æ¯</h3> |
| | | <div class="info-display"> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç»å®ç»å½è´¦å·ï¼</span> |
| | | <span class="info-value">{{ detailForm.bindUserName || '-' }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç»´æ¤äººï¼</span> |
| | | <span class="info-value">{{ detailForm.maintainer }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">ç»´æ¤æ¶é´ï¼</span> |
| | |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | <!-- æ´½è°è¿åº¦è®°å½ --> |
| | | <div class="detail-section"> |
| | | <div class="section-header"> |
| | | <h3 class="section-title">æ´½è°è¿åº¦è®°å½</h3> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openNegotiationDialog(detailForm)"> |
| | | æ·»å è¿åº¦ |
| | | </el-button> |
| | | </div> |
| | | <el-table :data="negotiationRecords" |
| | | <h3 class="section-title">è¿3å¹´è´¢å¡æ¥è¡¨</h3> |
| | | <el-table :data="detailReports" |
| | | border |
| | | size="small" |
| | | style="width: 100%"> |
| | | <el-table-column prop="followUpTime" |
| | | label="è·è¿æ¶é´" |
| | | width="160" /> |
| | | <el-table-column prop="followUpMethod" |
| | | label="è·è¿æ¹å¼" |
| | | width="100" /> |
| | | <el-table-column prop="followUpLevel" |
| | | label="è·è¿ç¨åº¦" /> |
| | | <el-table-column prop="followerUserName" |
| | | label="è·è¿äºº" |
| | | width="100" /> |
| | | <el-table-column prop="content" |
| | | label="å
容" |
| | | <el-table-column prop="reportYear" |
| | | label="年度" |
| | | width="90" |
| | | align="center" /> |
| | | <el-table-column prop="reportName" |
| | | label="æ¥è¡¨åç§°" |
| | | min-width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="éä»¶" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button type="info" |
| | | link |
| | | @click="openAttachmentDialog(row)"> |
| | | <el-icon> |
| | | <Paperclip /> |
| | | </el-icon> |
| | | éä»¶ |
| | | <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : 'ä¸ä¼ ' }} --> |
| | | </el-button> |
| | | <el-table-column label="æ¥è¡¨éä»¶" |
| | | min-width="260"> |
| | | <template #default="scope"> |
| | | <FileUpload v-model="scope.row.reportFiles" |
| | | disabled |
| | | :is-show-tip="false" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="150" |
| | | align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editNegotiationRecord(row, $index)"> |
| | | ä¿®æ¹ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteNegotiationRecord(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="uploadUserName" |
| | | label="ä¸ä¼ 人" |
| | | width="110" /> |
| | | <el-table-column prop="updateTime" |
| | | label="ä¸ä¼ æ¶é´" |
| | | width="170" /> |
| | | </el-table> |
| | | <div v-if="negotiationRecords.length === 0" |
| | | class="no-records"> |
| | | ææ æ´½è°è¿åº¦è®°å½ |
| | | </div> |
| | | <div v-if="detailReports.length === 0" |
| | | class="no-record">ææ è´¢å¡æ¥è¡¨</div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDetailDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- éä»¶ä¸ä¼ å¼¹çª --> |
| | | <el-dialog title="é件管ç" |
| | | v-model="attachmentDialogVisible" |
| | | width="600px" |
| | | @close="closeAttachmentDialog"> |
| | | <div class="attachment-section"> |
| | | <div class="upload-area"> |
| | | <el-upload ref="attachmentUploadRef" |
| | | :action="getAttachmentUploadUrl()" |
| | | :headers="attachmentUploadHeaders" |
| | | :file-list="currentAttachmentList" |
| | | :on-success="handleAttachmentSuccess" |
| | | :on-error="handleAttachmentError" |
| | | :on-remove="handleAttachmentRemove" |
| | | :before-upload="beforeAttachmentUpload" |
| | | multiple |
| | | :limit="10" |
| | | name="files"> |
| | | <el-button type="primary"> |
| | | <el-icon> |
| | | <Upload /> |
| | | </el-icon> |
| | | ä¸ä¼ éä»¶ |
| | | </el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æä¸ä¼ å¾çãææ¡£çæä»¶ï¼å个æä»¶ä¸è¶
è¿50MB |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </div> |
| | | <div v-if="currentAttachmentList.length > 0" |
| | | class="attachment-list"> |
| | | <h4>å·²ä¸ä¼ éä»¶ï¼</h4> |
| | | <el-table :data="currentAttachmentList" |
| | | border |
| | | size="small"> |
| | | <el-table-column prop="name" |
| | | label="æä»¶å" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="size" |
| | | label="大å°" |
| | | width="100"> |
| | | <template #default="{ row }"> |
| | | {{ formatFileSize(row.size) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="downloadAttachment(row)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | <el-button type="danger" |
| | | link |
| | | @click="deleteAttachment(row, $index)"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <div v-else |
| | | class="no-attachment"> |
| | | ææ éä»¶ |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeAttachmentDialog">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { Search, Close } from "@element-plus/icons-vue"; |
| | | import { listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | addCustomerFollow, |
| | | updateCustomerFollow, |
| | | delCustomerFollow, |
| | | addReturnVisit, |
| | | getReturnVisit, |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, backCustomer} from "@/api/basicData/customer.js"; |
| | | listCustomerFinancialReport, |
| | | saveCustomerFinancialReport, |
| | | delCustomerFinancialReport, |
| | | } from "@/api/basicData/customerFinancialReport.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FileUpload from "@/components/FileUpload/index.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | | // å访æéç¸å
³ |
| | | const reminderDialogVisible = ref(false); |
| | | const reminderFormRef = ref(); |
| | | const currentCustomerId = ref(); |
| | | const reminderForm = reactive({ |
| | | customerName: "", |
| | | reminderSwitch: false, |
| | | reminderContent: "", |
| | | reminderTime: "", |
| | | }); |
| | | const reminderRules = { |
| | | reminderContent: [ |
| | | { required: true, message: "请è¾å
¥æéå
容", trigger: "blur" }, |
| | | ], |
| | | reminderTime: [ |
| | | { required: true, message: "è¯·éæ©æéæ¶é´", trigger: "change" }, |
| | | ], |
| | | }; |
| | | |
| | | // æ´½è°è¿åº¦ç¸å
³ |
| | | const negotiationDialogVisible = ref(false); |
| | | const negotiationFormRef = ref(); |
| | | const negotiationForm = reactive({ |
| | | customerName: "", |
| | | customerId: "", |
| | | followUpMethod: "", |
| | | followUpLevel: "", |
| | | followUpTime: "", |
| | | followerUserName: "", |
| | | content: "", |
| | | }); |
| | | const negotiationRules = { |
| | | followUpMethod: [ |
| | | { required: true, message: "è¯·éæ©è·è¿æ¹å¼", trigger: "change" }, |
| | | ], |
| | | followUpLevel: [ |
| | | { required: true, message: "è¯·éæ©è·è¿ç¨åº¦", trigger: "change" }, |
| | | ], |
| | | followUpTime: [ |
| | | { required: true, message: "è¯·éæ©è·è¿æ¶é´", trigger: "change" }, |
| | | ], |
| | | content: [{ required: true, message: "请è¾å
¥å
容", trigger: "blur" }], |
| | | }; |
| | | |
| | | // 详æ
ç¸å
³ |
| | | const detailDialogVisible = ref(false); |
| | | const detailReports = ref([]); |
| | | const detailForm = reactive({ |
| | | customerName: "", |
| | | customerType: "", |
| | |
| | | bankCode: "", |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | bindUserId: "", |
| | | bindUserName: "", |
| | | maintainer: "", |
| | | maintenanceTime: "", |
| | | }); |
| | | const negotiationRecords = ref([]); |
| | | |
| | | // éä»¶ç¸å
³ |
| | | const attachmentDialogVisible = ref(false); |
| | | const attachmentUploadRef = ref(); |
| | | const currentAttachmentList = ref([]); |
| | | const currentFollowRecord = ref({}); |
| | | const attachmentUploadHeaders = { Authorization: "Bearer " + getToken() }; |
| | | |
| | | // 卿æå»ºä¸ä¼ URL |
| | | const getAttachmentUploadUrl = () => { |
| | | const baseUrl = |
| | | import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload"; |
| | | return currentFollowRecord.value.id |
| | | ? `${baseUrl}/${currentFollowRecord.value.id}` |
| | | : baseUrl; |
| | | }; |
| | | // è¿3å¹´è´¢å¡æ¥è¡¨ç¸å
³ |
| | | const reportDialogVisible = ref(false); |
| | | const reportSaving = ref(false); |
| | | const reportCustomer = reactive({ id: "", customerName: "" }); |
| | | const reportRows = ref([]); |
| | | const nowYear = new Date().getFullYear(); |
| | | const reportYears = [nowYear, nowYear - 1, nowYear - 2]; |
| | | const reportForm = reactive({ reportYear: nowYear, reportName: "", reportFiles: "" }); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | |
| | | }, |
| | | { |
| | | label: "å°ååèç³»æ¹å¼", |
| | | prop: "companyAddress", |
| | | prop: "addressPhone", |
| | | width: 250, |
| | | }, |
| | | { |
| | |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è·è¿è¿åº¦", |
| | | prop: "followUpLevel", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "è·è¿æ¶é´", |
| | | prop: "followUpTime", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "é¶è¡åºæ¬æ·", |
| | | prop: "basicBankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "é¶è¡è´¦å·", |
| | | prop: "bankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "弿·è¡å·", |
| | | prop: "bankCode", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainer", |
| | | }, |
| | | { |
| | | label: "å®¢æ·æ¥æº", |
| | | prop: "type", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "å
¬æµ·"; |
| | | } |
| | | return "ç§æµ·"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "warning"; |
| | | } |
| | | return "success"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | | width: 100, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 290, |
| | | width: 240, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | showHide: row => !isCustomerEditLocked(row), |
| | | showHide: () => proxy.$auth.hasPermi("basic:customer:edit"), |
| | | clickFun: row => { |
| | | if (isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("已被é宿¨¡åå¼ç¨çç§æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ·»å æ´½è°è¿åº¦", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openNegotiationDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å访æé", |
| | | name: "è¿3å¹´è´¢å¡æ¥è¡¨", |
| | | type: "text", |
| | | showHide: () => proxy.$auth.hasPermi("basic:customer:report"), |
| | | clickFun: row => { |
| | | openReminderDialog(row); |
| | | openReportDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | showHide: () => proxy.$auth.hasPermi("basic:customer:query"), |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const formYYs = ref({ |
| | | // å
¶ä»å段... |
| | | contactList: [ |
| | | { |
| | | contactPerson: "", |
| | |
| | | companyPhone: "", |
| | | contactPerson: "", |
| | | contactPhone: "", |
| | | bindUserId: "", |
| | | maintainer: "", |
| | | maintenanceTime: "", |
| | | basicBankAccount: "", |
| | |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPerson: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintenanceTime: [ |
| | | { required: false, message: "è¯·éæ©", trigger: "change" }, |
| | |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | | // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª |
| | | const isValid = |
| | | file.type === |
| | |
| | | }, |
| | | // æä»¶ä¸ä¼ æåæ¶çåè° |
| | | onSuccess: (response, file, fileList) => { |
| | | console.log("ä¸ä¼ æå", response, file, fileList); |
| | | upload.isUploading = false; |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | |
| | | }, |
| | | // æä»¶ä¸ä¼ 失败æ¶çåè° |
| | | onError: (error, file, fileList) => { |
| | | console.error("ä¸ä¼ 失败", error, file, fileList); |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }, |
| | | // æä»¶ä¸ä¼ è¿åº¦åè° |
| | | onProgress: (event, file, fileList) => { |
| | | onProgress: event => { |
| | | console.log("ä¸ä¼ ä¸...", event.percent); |
| | | }, |
| | | }); |
| | |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }).catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | if (type === "edit" && isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("已被é宿¨¡åå¼ç¨çç§æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainer = userStore.nickName; |
| | |
| | | if (type === "edit") { |
| | | getCustomer(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | formYYs.value.contactList = res.data.contactPerson |
| | | formYYs.value.contactList = (res.data.contactPerson || "") |
| | | .split(",") |
| | | .filter(item => item) |
| | | .map((item, index) => { |
| | | return { |
| | | contactPerson: item, |
| | | contactPhone: res.data.contactPhone.split(",")[index], |
| | | contactPhone: (res.data.contactPhone || "").split(",")[index], |
| | | }; |
| | | }); |
| | | if (formYYs.value.contactList.length === 0) { |
| | | formYYs.value.contactList = [{ contactPerson: "", contactPhone: "" }]; |
| | | } |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | const isCustomerEditLocked = row => { |
| | | const salesReferenced = Number(row?.salesReferenceFlag || 0) === 1; |
| | | const customerType = Number(row?.type); |
| | | const usageStatus = Number(row?.usageStatus || 0); |
| | | return salesReferenced && (customerType === 0 || (customerType === 1 && usageStatus === 1)); |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | addCustomer(form.value).then(res => { |
| | | addCustomer(form.value).then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | form.value.contactPhone = formYYs.value.contactList |
| | | .map(item => item.contactPhone) |
| | | .join(","); |
| | | updateCustomer(form.value).then(res => { |
| | | updateCustomer(form.value).then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {type: 0}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", { type: 0 }, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | }); |
| | | }; |
| | | |
| | | const back = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | // æå¼è¿3å¹´è´¢å¡æ¥è¡¨å¼¹çª |
| | | const openReportDialog = row => { |
| | | reportCustomer.id = row.id; |
| | | reportCustomer.customerName = row.customerName; |
| | | resetReportForm(); |
| | | loadReports(row.id); |
| | | reportDialogVisible.value = true; |
| | | }; |
| | | |
| | | const loadReports = customerId => { |
| | | listCustomerFinancialReport({ customerId }).then(res => { |
| | | reportRows.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const reportStatusText = status => { |
| | | if (status === 1) return "å·²éè¿"; |
| | | if (status === 2) return "å·²éå"; |
| | | return "å¾
审æ¹"; |
| | | }; |
| | | const reportStatusTag = status => { |
| | | if (status === 1) return "success"; |
| | | if (status === 2) return "danger"; |
| | | return "warning"; |
| | | }; |
| | | |
| | | const resetReportForm = () => { |
| | | reportForm.reportYear = nowYear; |
| | | reportForm.reportName = ""; |
| | | reportForm.reportFiles = ""; |
| | | }; |
| | | |
| | | const editReport = row => { |
| | | reportForm.reportYear = row.reportYear; |
| | | reportForm.reportName = row.reportName; |
| | | reportForm.reportFiles = row.reportFiles; |
| | | }; |
| | | |
| | | const saveReport = () => { |
| | | if (!reportForm.reportFiles) { |
| | | proxy.$modal.msgWarning("请ä¸ä¼ è´¢å¡æ¥è¡¨éä»¶"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("éä¸ç客æ·å°æµå
¥å
¬æµ·ï¼æ¯å¦ç¡®è®¤ï¼", "æµå
¥å
¬æµ·æç¤º", { |
| | | reportSaving.value = true; |
| | | saveCustomerFinancialReport({ |
| | | customerId: reportCustomer.id, |
| | | reportYear: reportForm.reportYear, |
| | | reportName: reportForm.reportName, |
| | | reportFiles: reportForm.reportFiles, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("ä¿åæå"); |
| | | resetReportForm(); |
| | | loadReports(reportCustomer.id); |
| | | }).finally(() => { |
| | | reportSaving.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const deleteReport = row => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¯¥å¹´åº¦è´¢å¡æ¥è¡¨åï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | return Promise.all(ids.map(id => backCustomer(id))) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æµå
¥å
¬æµ·æå"); |
| | | selectedRows.value = []; |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | delCustomerFinancialReport(row.id).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | loadReports(reportCustomer.id); |
| | | }); |
| | | }) |
| | | .catch(error => { |
| | | if (error === "cancel" || error === "close") { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼å访æéå¼¹çª |
| | | const openReminderDialog = row => { |
| | | currentCustomerId.value = row.id; |
| | | reminderForm.customerName = row.customerName; |
| | | reminderForm.reminderSwitch = false; |
| | | reminderForm.reminderContent = ""; |
| | | reminderForm.reminderTime = ""; |
| | | |
| | | // å°è¯è·åå·²æçå访æé |
| | | getReturnVisit(row.id) |
| | | .then(res => { |
| | | if (res.code === 200 && res.data) { |
| | | reminderForm.reminderSwitch = res.data.isEnabled === 1; |
| | | reminderForm.reminderContent = res.data.content; |
| | | reminderForm.reminderTime = res.data.reminderTime; |
| | | reminderForm.id = res.data.id; |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åå访æé失败:", error); |
| | | }); |
| | | |
| | | reminderDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éå访æéå¼¹çª |
| | | const closeReminderDialog = () => { |
| | | proxy.resetForm("reminderFormRef"); |
| | | reminderDialogVisible.value = false; |
| | | }; |
| | | const submitvalue = ref({}); |
| | | |
| | | // æäº¤å访æé |
| | | const submitReminderForm = () => { |
| | | console.log("æäº¤å访æéæ°æ®:", userStore.id, userStore); |
| | | proxy.$refs.reminderFormRef.validate(valid => { |
| | | if (valid) { |
| | | if (reminderForm.id) { |
| | | submitvalue.value = { |
| | | id: reminderForm.id, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | | remindUserId: userStore.id, |
| | | }; |
| | | } else { |
| | | submitvalue.value = { |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | | remindUserId: userStore.id, |
| | | }; |
| | | } |
| | | |
| | | // è°ç¨æ¥å£ |
| | | addReturnVisit(submitvalue.value) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("å访æé设置æå"); |
| | | closeReminderDialog(); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "设置失败"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("设置å访æé失败:", error); |
| | | proxy.$modal.msgError("设置失败"); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æå¼æ´½è°è¿åº¦å¼¹çª |
| | | const openNegotiationDialog = row => { |
| | | negotiationForm.customerName = row.customerName; |
| | | negotiationForm.customerId = row.id; |
| | | negotiationForm.followUpMethod = ""; |
| | | negotiationForm.followUpLevel = ""; |
| | | negotiationForm.followUpTime = ""; |
| | | negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº |
| | | negotiationForm.content = ""; |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³éæ´½è°è¿åº¦å¼¹çª |
| | | const closeNegotiationDialog = () => { |
| | | proxy.resetForm("negotiationFormRef"); |
| | | // æ¸
é¤ç¼è¾ç¶æ |
| | | delete negotiationForm.editIndex; |
| | | delete negotiationForm.id; |
| | | negotiationDialogVisible.value = false; |
| | | }; |
| | | |
| | | // æäº¤æ´½è°è¿åº¦ |
| | | const submitNegotiationForm = () => { |
| | | proxy.$refs.negotiationFormRef.validate(valid => { |
| | | if (valid) { |
| | | // å¤ææ¯æ°å¢è¿æ¯ä¿®æ¹ |
| | | const isEdit = negotiationForm.editIndex !== undefined; |
| | | |
| | | if (isEdit) { |
| | | // ä¿®æ¹æä½ |
| | | updateCustomerFollow(negotiationForm).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | getCustomer(negotiationForm.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | | }); |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeNegotiationDialog(); |
| | | } else { |
| | | // æ°å¢æä½ |
| | | console.log("æäº¤æ´½è°è¿åº¦æ°æ®:", negotiationForm); |
| | | addCustomerFollow(negotiationForm).then(res => { |
| | | // æ·»å æååæ´æ°è¯¦æ
页é¢çè¿åº¦è®°å½ |
| | | const newRecord = { |
| | | followUpTime: negotiationForm.followUpTime, |
| | | followUpMethod: negotiationForm.followUpMethod, |
| | | followUpLevel: negotiationForm.followUpLevel, |
| | | followerUserName: negotiationForm.followerUserName, |
| | | content: negotiationForm.content, |
| | | }; |
| | | negotiationRecords.value.unshift(newRecord); |
| | | |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeNegotiationDialog(); |
| | | getList(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | const closeReportDialog = () => { |
| | | reportDialogVisible.value = false; |
| | | reportRows.value = []; |
| | | resetReportForm(); |
| | | }; |
| | | |
| | | // æå¼è¯¦æ
å¼¹çª |
| | | const openDetailDialog = row => { |
| | | getCustomer(row.id).then(res => { |
| | | // å¡«å
客æ·åºæ¬ä¿¡æ¯ |
| | | Object.assign(detailForm, res.data); |
| | | |
| | | // è·åæ´½è°è¿åº¦è®°å½ |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | |
| | | const bindUser = userList.value.find(item => item.userId === res.data.bindUserId); |
| | | detailForm.bindUserName = bindUser ? bindUser.nickName : ""; |
| | | detailReports.value = []; |
| | | detailDialogVisible.value = true; |
| | | listCustomerFinancialReport({ customerId: row.id }).then(reportRes => { |
| | | detailReports.value = reportRes.data || []; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | |
| | | detailDialogVisible.value = false; |
| | | }; |
| | | |
| | | // ä¿®æ¹æ´½è°è®°å½ |
| | | const editNegotiationRecord = (row, index) => { |
| | | // å°å½åè®°å½æ°æ®å¡«å
å°è¡¨å |
| | | Object.assign(negotiationForm, { |
| | | customerName: row.customerName, |
| | | customerId: row.customerId, |
| | | followUpMethod: row.followUpMethod, |
| | | followUpLevel: row.followUpLevel, |
| | | followUpTime: row.followUpTime, |
| | | followerUserName: row.followerUserName, |
| | | content: row.content, |
| | | id: row.id, // è®°å½IDç¨äºæ´æ° |
| | | editIndex: index, // è®°å½ç´¢å¼ç¨äºæ¬å°æ´æ° |
| | | }); |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å 餿´½è°è®°å½ |
| | | const deleteNegotiationRecord = (row, index) => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡æ´½è°è®°å½åï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è¿éå¯ä»¥è°ç¨å 餿¥å£ |
| | | // å®é
项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ |
| | | // 示ä¾ï¼deleteCustomerFollow(row.id).then(() => { |
| | | // negotiationRecords.value.splice(index, 1); |
| | | // proxy.$modal.msgSuccess("å 餿å"); |
| | | // }); |
| | | delCustomerFollow(row.id).then(() => { |
| | | // å 餿ååæ´æ°æ¬å°æ°æ® |
| | | getCustomer(row.customerId).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | negotiationRecords.value = res.data.followUpList || []; |
| | | }); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }); |
| | | // æ¬å°å é¤ï¼æ¨¡æï¼ |
| | | negotiationRecords.value.splice(index, 1); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶å é¤"); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const openAttachmentDialog = row => { |
| | | currentFollowRecord.value = row; |
| | | // 转æ¢ä¸ºç¬¦åElement Plus fileListæ ¼å¼çæ°ç» |
| | | currentAttachmentList.value = (row.fileList || []).map((file, index) => ({ |
| | | name: file.fileName, |
| | | url: file.fileUrl, |
| | | size: file.fileSize, |
| | | id: file.id, |
| | | uid: file.id || index, |
| | | status: "success", |
| | | })); |
| | | |
| | | attachmentDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å
³ééä»¶å¼¹çª |
| | | const closeAttachmentDialog = () => { |
| | | attachmentDialogVisible.value = false; |
| | | currentFollowRecord.value = {}; |
| | | currentAttachmentList.value = []; |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ æå |
| | | const handleAttachmentSuccess = (response, file, fileList) => { |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | // æ´æ°å½åè®°å½çéä»¶å表 |
| | | currentAttachmentList.value = fileList.map(item => ({ |
| | | name: item.name, |
| | | size: item.size, |
| | | url: item.response?.data?.url || item.url, |
| | | id: item.response?.data?.id, |
| | | uid: item.uid, |
| | | status: "success", |
| | | })); |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [...currentAttachmentList.value]; |
| | | } |
| | | } else { |
| | | proxy.$modal.msgError(response.msg || "ä¸ä¼ 失败"); |
| | | } |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ 失败 |
| | | const handleAttachmentError = (error, file, fileList) => { |
| | | console.error("ä¸ä¼ 失败:", error); |
| | | proxy.$modal.msgError("ä¸ä¼ 失败"); |
| | | }; |
| | | |
| | | // éä»¶ç§»é¤ |
| | | const handleAttachmentRemove = (file, fileList) => { |
| | | currentAttachmentList.value = fileList; |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [...fileList]; |
| | | } |
| | | }; |
| | | |
| | | // éä»¶ä¸ä¼ åæ ¡éª |
| | | const beforeAttachmentUpload = file => { |
| | | const maxSize = 50 * 1024 * 1024; // 50MB |
| | | if (file.size > maxSize) { |
| | | proxy.$modal.msgError("æä»¶å¤§å°ä¸è½è¶
è¿50MB"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // æ ¼å¼åæä»¶å¤§å° |
| | | const formatFileSize = size => { |
| | | if (size < 1024) { |
| | | return size + " B"; |
| | | } else if (size < 1024 * 1024) { |
| | | return (size / 1024).toFixed(2) + " KB"; |
| | | } else { |
| | | return (size / (1024 * 1024)).toFixed(2) + " MB"; |
| | | } |
| | | }; |
| | | |
| | | // ä¸è½½éä»¶ |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| | | }; |
| | | |
| | | // å é¤éä»¶ |
| | | const deleteAttachment = (row, index) => { |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªéä»¶åï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // è°ç¨å端æ¥å£å é¤éä»¶ |
| | | const deleteUrl = |
| | | import.meta.env.VITE_APP_BASE_API + |
| | | "/basic/customer-follow/file/" + |
| | | row.id; |
| | | fetch(deleteUrl, { |
| | | method: "DELETE", |
| | | headers: { |
| | | Authorization: "Bearer " + getToken(), |
| | | "Content-Type": "application/json", |
| | | }, |
| | | }) |
| | | .then(response => response.json()) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | // å 餿ååæ´æ°æ¬å°æä»¶å表 |
| | | currentAttachmentList.value.splice(index, 1); |
| | | // æ´æ°åè®°å½ä¸çfilesåæ®µ |
| | | if (currentFollowRecord.value) { |
| | | currentFollowRecord.value.files = [ |
| | | ...currentAttachmentList.value, |
| | | ]; |
| | | } |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("å é¤é件失败:", error); |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶å é¤"); |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | onMounted(() => { |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | getList(); |
| | | }); |
| | | </script> |
| | |
| | | padding: 15px; |
| | | background-color: #f9f9f9; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .section-title { |
| | |
| | | word-break: break-word; |
| | | } |
| | | |
| | | .no-records { |
| | | .no-record { |
| | | text-align: center; |
| | | padding: 30px; |
| | | padding: 20px; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .attachment-section { |
| | | .upload-area { |
| | | margin-bottom: 20px; |
| | | padding: 20px; |
| | | background-color: #f9f9f9; |
| | | border-radius: 4px; |
| | | border: 1px dashed #d9d9d9; |
| | | .report-customer { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .el-upload__tip { |
| | | margin-top: 10px; |
| | | color: #909399; |
| | | } |
| | | } |
| | | .report-form { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .attachment-list { |
| | | h4 { |
| | | margin: 0 0 10px 0; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | } |
| | | |
| | | .no-attachment { |
| | | text-align: center; |
| | | padding: 40px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | .report-actions { |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| | |
| | | <!-- 页颿 é¢ --> |
| | | <div class="page-header"> |
| | | <div class="header-title"> |
| | | <el-icon class="title-icon"><Setting /></el-icon> |
| | | <el-icon class="title-icon"> |
| | | <Setting /> |
| | | </el-icon> |
| | | <span>å®¡æ¹æµç¨é
ç½®</span> |
| | | </div> |
| | | <div class="header-desc">为ä¸å审æ¹ç±»åé
ç½®å®¡æ¹æµç¨å审æ¹äºº</div> |
| | | </div> |
| | | |
| | | <!-- 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ --> |
| | | <div class="type-tabs"> |
| | | <div |
| | | v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()" |
| | | > |
| | | <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <div v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()"> |
| | | <el-icon :size="14" |
| | | :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <component :is="type.icon" /> |
| | | </el-icon> |
| | | <span class="tab-name">{{ type.label }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 主è¦å
容åºå --> |
| | | <el-card class="config-card" shadow="hover" v-loading="loading"> |
| | | <el-card class="config-card" |
| | | shadow="hover" |
| | | v-loading="loading"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <div class="header-left"> |
| | | <div class="type-icon" :style="{ backgroundColor: getTypeColor(currentApproveType) }"> |
| | | <el-icon :size="20" color="#fff"><component :is="getTypeIcon(currentApproveType)" /></el-icon> |
| | | <div class="type-icon" |
| | | :style="{ backgroundColor: getTypeColor(currentApproveType) }"> |
| | | <el-icon :size="20" |
| | | color="#fff"> |
| | | <component :is="getTypeIcon(currentApproveType)" /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="header-info"> |
| | | <span class="type-name">{{ currentApproveTypeName }}</span> |
| | | <el-tag :type="approverList.length > 0 ? 'success' : 'warning'" size="small" effect="light"> |
| | | <el-tag :type="approverList.length > 0 ? 'success' : 'warning'" |
| | | size="small" |
| | | effect="light"> |
| | | {{ approverList.length > 0 ? `å·²é
ç½® ${approverList.length} 个审æ¹äºº` : 'æªé
置审æ¹äºº' }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | <div class="header-actions" v-if="approverList.length > 0"> |
| | | <el-button @click="handleReset" size="default"> |
| | | <el-icon><RefreshLeft /></el-icon> |
| | | <div class="header-actions" |
| | | v-if="approverList.length > 0"> |
| | | <el-button @click="handleReset" |
| | | size="default"> |
| | | <el-icon> |
| | | <RefreshLeft /> |
| | | </el-icon> |
| | | éç½® |
| | | </el-button> |
| | | <el-button type="primary" @click="handleSave" :loading="saveLoading" size="default"> |
| | | <el-icon><Check /></el-icon> |
| | | <el-button type="primary" |
| | | @click="handleSave" |
| | | :loading="saveLoading" |
| | | size="default"> |
| | | <el-icon> |
| | | <Check /> |
| | | </el-icon> |
| | | ä¿åé
ç½® |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- å®¡æ¹æµç¨å±ç¤º --> |
| | | <div class="flow-wrapper" v-if="approverList.length > 0"> |
| | | <div class="flow-wrapper" |
| | | v-if="approverList.length > 0"> |
| | | <div class="flow-container"> |
| | | <div |
| | | v-for="(item, index) in approverList" |
| | | :key="index" |
| | | class="flow-item" |
| | | > |
| | | <div v-for="(item, index) in approverList" |
| | | :key="index" |
| | | class="flow-item"> |
| | | <!-- 审æ¹èç¹å¡ç --> |
| | | <div class="node-card" :class="{ 'empty': !item.approverId }"> |
| | | <div class="node-card" |
| | | :class="{ 'empty': !item.approverId }"> |
| | | <!-- é¡¶é¨åºå·åçº§å« --> |
| | | <div class="node-badge">{{ index + 1 }}</div> |
| | | |
| | | <!-- 头ååºå --> |
| | | <div class="node-avatar-section"> |
| | | <div |
| | | class="node-avatar" |
| | | :class="{ 'has-user': item.approverId }" |
| | | :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}" |
| | | > |
| | | <div class="node-avatar" |
| | | :class="{ 'has-user': item.approverId }" |
| | | :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}"> |
| | | <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span> |
| | | <el-icon v-else :size="24"><User /></el-icon> |
| | | <el-icon v-else |
| | | :size="24"> |
| | | <User /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="node-level">{{ getLevelText(index) }}</div> |
| | | </div> |
| | | |
| | | <!-- éæ©åºå --> |
| | | <div class="node-select-section"> |
| | | <el-select |
| | | v-model="item.approverId" |
| | | placeholder="鿩审æ¹äºº" |
| | | filterable |
| | | size="default" |
| | | @change="(val) => handleApproverChange(val, item)" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | <el-select v-model="item.approverId" |
| | | placeholder="鿩审æ¹äºº" |
| | | filterable |
| | | size="default" |
| | | @change="(val) => handleApproverChange(val, item)"> |
| | | <el-option v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" /> |
| | | </el-select> |
| | | </div> |
| | | |
| | | <!-- æä½æé® --> |
| | | <div class="node-actions"> |
| | | <el-button |
| | | type="primary" |
| | | circle |
| | | :disabled="index === 0" |
| | | @click="moveLeft(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»" |
| | | > |
| | | <el-icon><ArrowLeft /></el-icon> |
| | | <el-button type="primary" |
| | | circle |
| | | :disabled="index === 0" |
| | | @click="moveLeft(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»"> |
| | | <el-icon> |
| | | <ArrowLeft /> |
| | | </el-icon> |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | circle |
| | | :disabled="index === approverList.length - 1" |
| | | @click="moveRight(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»" |
| | | > |
| | | <el-icon><ArrowRight /></el-icon> |
| | | <el-button type="primary" |
| | | circle |
| | | :disabled="index === approverList.length - 1" |
| | | @click="moveRight(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | title="åç§»"> |
| | | <el-icon> |
| | | <ArrowRight /> |
| | | </el-icon> |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | circle |
| | | @click="handleDelete(index)" |
| | | size="small" |
| | | class="action-btn" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | | <el-button type="danger" |
| | | circle |
| | | @click="handleDelete(index)" |
| | | size="small" |
| | | class="action-btn"> |
| | | <el-icon> |
| | | <Delete /> |
| | | </el-icon> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¿æ¥ç®å¤´ --> |
| | | <div class="arrow-connector" v-if="index < approverList.length - 1"> |
| | | <div class="arrow-connector" |
| | | v-if="index < approverList.length - 1"> |
| | | <div class="arrow-line"></div> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight /> |
| | | </el-icon> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ°å¢èç¹æé® - æ¾å¨æµç¨æå --> |
| | | <div class="add-node-item"> |
| | | <div class="arrow-connector" v-if="approverList.length > 0"> |
| | | <div class="arrow-connector" |
| | | v-if="approverList.length > 0"> |
| | | <div class="arrow-line"></div> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="add-node-card" @click="handleAdd"> |
| | | <div class="add-node-card" |
| | | @click="handleAdd"> |
| | | <div class="add-icon-wrapper"> |
| | | <el-icon :size="28"><Plus /></el-icon> |
| | | <el-icon :size="28"> |
| | | <Plus /> |
| | | </el-icon> |
| | | </div> |
| | | <span class="add-text">æ°å¢å®¡æ¹äºº</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div class="empty-state" v-else> |
| | | <div class="empty-state" |
| | | v-else> |
| | | <div class="empty-content"> |
| | | <div class="empty-icon-wrapper"> |
| | | <el-icon :size="48" color="#c0c4cc"><User /></el-icon> |
| | | <el-icon :size="48" |
| | | color="#c0c4cc"> |
| | | <User /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="empty-text">ææ å®¡æ¹äººé
ç½®</div> |
| | | <div class="empty-subtext">ç¹å»ä¸æ¹æé®æ·»å 第ä¸ä¸ªå®¡æ¹äºº</div> |
| | | <el-button type="primary" size="large" @click="handleAdd" class="empty-add-btn"> |
| | | <el-icon><Plus /></el-icon> |
| | | <el-button type="primary" |
| | | size="large" |
| | | @click="handleAdd" |
| | | class="empty-add-btn"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon> |
| | | æ°å¢å®¡æ¹äºº |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- åºé¨æç¤º --> |
| | | <div class="bottom-tips"> |
| | | <el-icon><InfoFilled /></el-icon> |
| | | <el-icon> |
| | | <InfoFilled /> |
| | | </el-icon> |
| | | <span>æç¤ºï¼æ¯ä¸ªæµç¨è³å°é
ç½®ä¸ä¸ªå®¡æ¹äººï¼å®¡æ¹æé¡ºåºæµè½¬ï¼å¯éè¿ç®å¤´è°æ´é¡ºåº</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { |
| | | Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting, |
| | | Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked, |
| | | Van, ArrowRight, User, InfoFilled |
| | | } from '@element-plus/icons-vue'; |
| | | import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement'; |
| | | import { userListNoPage } from '@/api/system/user'; |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | Plus, |
| | | ArrowLeft, |
| | | Delete, |
| | | Check, |
| | | RefreshLeft, |
| | | Setting, |
| | | Suitcase, |
| | | Calendar, |
| | | Location, |
| | | Money, |
| | | ShoppingCart, |
| | | DocumentChecked, |
| | | Van, |
| | | ArrowRight, |
| | | User, |
| | | InfoFilled, |
| | | } from "@element-plus/icons-vue"; |
| | | import { |
| | | getApproveProcessConfigNodeList, |
| | | addApproveProcessConfigNode, |
| | | } from "@/api/collaborativeApproval/approvalManagement"; |
| | | import { userListNoPage } from "@/api/system/user"; |
| | | |
| | | // å½åéä¸çæ ç¾é¡µ |
| | | const activeTab = ref('1'); |
| | | // å½åéä¸çæ ç¾é¡µ |
| | | const activeTab = ref("2"); |
| | | |
| | | // 审æ¹ç±»åé
ç½®æ°ç» |
| | | const approveTypes = [ |
| | | { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: '2', label: '请å管ç', icon: 'Calendar', color: '#67C23A' }, |
| | | { value: '3', label: 'åºå·®ç®¡ç', icon: 'Location', color: '#E6A23C' }, |
| | | { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | // 审æ¹ç±»åé
ç½®æ°ç» |
| | | const approveTypes = [ |
| | | // { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: "2", label: "请å管ç", icon: "Calendar", color: "#67C23A" }, |
| | | { value: "3", label: "åºå·®ç®¡ç", icon: "Location", color: "#E6A23C" }, |
| | | // { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | // { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | // { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | // { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | |
| | | // 审æ¹ç±»ååç§°æ å° |
| | | const approveTypeNameMap = { |
| | | 1: 'å
¬åºç®¡ç', |
| | | 2: '请å管ç', |
| | | 3: 'åºå·®ç®¡ç', |
| | | 4: 'æ¥é管ç', |
| | | 5: 'éè´å®¡æ¹', |
| | | 6: 'æ¥ä»·å®¡æ¹', |
| | | 7: 'å货审æ¹', |
| | | }; |
| | | // 审æ¹ç±»ååç§°æ å° |
| | | const approveTypeNameMap = { |
| | | // 1: 'å
¬åºç®¡ç', |
| | | 2: "请å管ç", |
| | | 3: "åºå·®ç®¡ç", |
| | | // 4: 'æ¥é管ç', |
| | | // 5: 'éè´å®¡æ¹', |
| | | // 6: 'æ¥ä»·å®¡æ¹', |
| | | // 7: 'å货审æ¹', |
| | | }; |
| | | |
| | | // 审æ¹ç±»å徿 æ å° |
| | | const typeIconMap = { |
| | | 1: 'Suitcase', |
| | | 2: 'Calendar', |
| | | 3: 'Location', |
| | | 4: 'Money', |
| | | 5: 'ShoppingCart', |
| | | 6: 'DocumentChecked', |
| | | 7: 'Van', |
| | | }; |
| | | // 审æ¹ç±»å徿 æ å° |
| | | const typeIconMap = { |
| | | 1: "Suitcase", |
| | | 2: "Calendar", |
| | | 3: "Location", |
| | | 4: "Money", |
| | | 5: "ShoppingCart", |
| | | 6: "DocumentChecked", |
| | | 7: "Van", |
| | | }; |
| | | |
| | | // 审æ¹ç±»åé¢è²æ å° |
| | | const typeColorMap = { |
| | | 1: '#409EFF', |
| | | 2: '#67C23A', |
| | | 3: '#E6A23C', |
| | | 4: '#F56C6C', |
| | | 5: '#909399', |
| | | 6: '#9B59B6', |
| | | 7: '#1ABC9C', |
| | | }; |
| | | // 审æ¹ç±»åé¢è²æ å° |
| | | const typeColorMap = { |
| | | 1: "#409EFF", |
| | | 2: "#67C23A", |
| | | 3: "#E6A23C", |
| | | 4: "#F56C6C", |
| | | 5: "#909399", |
| | | 6: "#9B59B6", |
| | | 7: "#1ABC9C", |
| | | }; |
| | | |
| | | // 头åé¢è²æ± |
| | | const avatarColors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#1ABC9C', '#FF6B6B', '#4ECDC4']; |
| | | // 头åé¢è²æ± |
| | | const avatarColors = [ |
| | | "#409EFF", |
| | | "#67C23A", |
| | | "#E6A23C", |
| | | "#F56C6C", |
| | | "#9B59B6", |
| | | "#1ABC9C", |
| | | "#FF6B6B", |
| | | "#4ECDC4", |
| | | ]; |
| | | |
| | | // å½å审æ¹ç±»ååç§° |
| | | const currentApproveTypeName = computed(() => { |
| | | return approveTypeNameMap[activeTab.value] || 'æªç¥ç±»å'; |
| | | }); |
| | | // å½å审æ¹ç±»ååç§° |
| | | const currentApproveTypeName = computed(() => { |
| | | return approveTypeNameMap[activeTab.value] || "æªç¥ç±»å"; |
| | | }); |
| | | |
| | | // å½å审æ¹ç±»å |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | // å½å审æ¹ç±»å |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | |
| | | // è·åç±»å徿 |
| | | const getTypeIcon = (type) => typeIconMap[type] || 'Setting'; |
| | | // è·åç±»å徿 |
| | | const getTypeIcon = type => typeIconMap[type] || "Setting"; |
| | | |
| | | // è·åç±»åé¢è² |
| | | const getTypeColor = (type) => typeColorMap[type] || '#409EFF'; |
| | | // è·åç±»åé¢è² |
| | | const getTypeColor = type => typeColorMap[type] || "#409EFF"; |
| | | |
| | | // è·å头åé¢è² |
| | | const getAvatarColor = (name) => { |
| | | if (!name) return '#C0C4CC'; |
| | | let hash = 0; |
| | | for (let i = 0; i < name.length; i++) { |
| | | hash = name.charCodeAt(i) + ((hash << 5) - hash); |
| | | } |
| | | return avatarColors[Math.abs(hash) % avatarColors.length]; |
| | | }; |
| | | // è·å头åé¢è² |
| | | const getAvatarColor = name => { |
| | | if (!name) return "#C0C4CC"; |
| | | let hash = 0; |
| | | for (let i = 0; i < name.length; i++) { |
| | | hash = name.charCodeAt(i) + ((hash << 5) - hash); |
| | | } |
| | | return avatarColors[Math.abs(hash) % avatarColors.length]; |
| | | }; |
| | | |
| | | // è·åçº§å«ææ¬ |
| | | const getLevelText = (index) => { |
| | | const texts = ['第ä¸çº§', '第äºçº§', '第ä¸çº§', '第å级', '第äºçº§', '第å
级', '第ä¸çº§', '第å
«çº§']; |
| | | return texts[index] || `第${index + 1}级`; |
| | | }; |
| | | // è·åçº§å«ææ¬ |
| | | const getLevelText = index => { |
| | | const texts = [ |
| | | "第ä¸çº§", |
| | | "第äºçº§", |
| | | "第ä¸çº§", |
| | | "第å级", |
| | | "第äºçº§", |
| | | "第å
级", |
| | | "第ä¸çº§", |
| | | "第å
«çº§", |
| | | ]; |
| | | return texts[index] || `第${index + 1}级`; |
| | | }; |
| | | |
| | | // 审æ¹äººå表ï¼ç宿¥å£ï¼ |
| | | const userList = ref([]); |
| | | // 审æ¹äººå表ï¼ç宿¥å£ï¼ |
| | | const userList = ref([]); |
| | | |
| | | // 审æ¹äººå表 |
| | | const approverList = ref([]); |
| | | // 审æ¹äººå表 |
| | | const approverList = ref([]); |
| | | |
| | | // åå§æ°æ®ï¼ç¨äºéç½® |
| | | const originalList = ref([]); |
| | | // åå§æ°æ®ï¼ç¨äºéç½® |
| | | const originalList = ref([]); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const saveLoading = ref(false); |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const saveLoading = ref(false); |
| | | |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | loadData(); |
| | | }; |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | loadData(); |
| | | }; |
| | | |
| | | // å 载审æ¹é
ç½®æ°æ®ï¼æ¨¡æï¼ |
| | | const loadData = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getApproveProcessConfigNodeList(currentApproveType.value); |
| | | const source = Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.rows) |
| | | // å 载审æ¹é
ç½®æ°æ®ï¼æ¨¡æï¼ |
| | | const loadData = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await getApproveProcessConfigNodeList(currentApproveType.value); |
| | | const source = Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.rows) |
| | | ? res.rows |
| | | : Array.isArray(res?.data?.records) |
| | | ? res.data.records |
| | | : []; |
| | | const data = source.map((item, index) => ({ |
| | | ...item, |
| | | sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1, |
| | | })); |
| | | approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); |
| | | originalList.value = JSON.parse(JSON.stringify(approverList.value)); |
| | | } catch (error) { |
| | | approverList.value = []; |
| | | originalList.value = []; |
| | | ElMessage.error('å 载审æ¹é
置失败'); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | ? res.data.records |
| | | : []; |
| | | const data = source.map((item, index) => ({ |
| | | ...item, |
| | | sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1, |
| | | })); |
| | | approverList.value = data.sort( |
| | | (a, b) => (a.sortOrder || 0) - (b.sortOrder || 0) |
| | | ); |
| | | originalList.value = JSON.parse(JSON.stringify(approverList.value)); |
| | | } catch (error) { |
| | | approverList.value = []; |
| | | originalList.value = []; |
| | | ElMessage.error("å 载审æ¹é
置失败"); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const loadUserList = async () => { |
| | | try { |
| | | const res = await userListNoPage(); |
| | | userList.value = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (error) { |
| | | userList.value = []; |
| | | ElMessage.error('å 载人åå表失败'); |
| | | } |
| | | }; |
| | | const loadUserList = async () => { |
| | | try { |
| | | const res = await userListNoPage(); |
| | | userList.value = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (error) { |
| | | userList.value = []; |
| | | ElMessage.error("å 载人åå表失败"); |
| | | } |
| | | }; |
| | | |
| | | // 审æ¹äººéæ©åå |
| | | const handleApproverChange = (userId, row) => { |
| | | const user = userList.value.find((u) => u.userId === userId); |
| | | if (user) { |
| | | row.approverName = user.nickName; |
| | | } |
| | | }; |
| | | // 审æ¹äººéæ©åå |
| | | const handleApproverChange = (userId, row) => { |
| | | const user = userList.value.find(u => u.userId === userId); |
| | | if (user) { |
| | | row.approverName = user.nickName; |
| | | } |
| | | }; |
| | | |
| | | // æ°å¢å®¡æ¹äºº |
| | | const handleAdd = () => { |
| | | const newOrder = approverList.value.length + 1; |
| | | approverList.value.push({ |
| | | id: null, |
| | | approveType: currentApproveType.value, |
| | | approverId: null, |
| | | approverName: '', |
| | | sortOrder: newOrder, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤å®¡æ¹äºº |
| | | const handleDelete = (index) => { |
| | | ElMessageBox.confirm('ç¡®å®å é¤è¯¥å®¡æ¹äººåï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | approverList.value.splice(index, 1); |
| | | approverList.value.forEach((item, idx) => { |
| | | item.sortOrder = idx + 1; |
| | | }); |
| | | ElMessage.success('å 餿å'); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // åç§» |
| | | const moveLeft = (index) => { |
| | | if (index === 0) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index - 1]; |
| | | approverList.value[index - 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index - 1].sortOrder = index; |
| | | }; |
| | | |
| | | // åç§» |
| | | const moveRight = (index) => { |
| | | if (index === approverList.value.length - 1) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index + 1]; |
| | | approverList.value[index + 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index + 1].sortOrder = index + 2; |
| | | }; |
| | | |
| | | // ä¿åé
ç½® |
| | | const handleSave = async () => { |
| | | if (approverList.value.length === 0) { |
| | | ElMessage.warning('请è³å°é
ç½®ä¸ä¸ªå®¡æ¹äºº'); |
| | | return; |
| | | } |
| | | |
| | | const hasEmptyApprover = approverList.value.some((item) => !item.approverId); |
| | | if (hasEmptyApprover) { |
| | | ElMessage.warning('è¯·éæ©ææå®¡æ¹äºº'); |
| | | return; |
| | | } |
| | | |
| | | const approverIds = approverList.value.map((item) => item.approverId); |
| | | const uniqueIds = [...new Set(approverIds)]; |
| | | if (uniqueIds.length !== approverIds.length) { |
| | | ElMessage.warning('审æ¹äººä¸è½éå¤'); |
| | | return; |
| | | } |
| | | |
| | | saveLoading.value = true; |
| | | try { |
| | | const payload = approverList.value.map((item, index) => ({ |
| | | // æ°å¢å®¡æ¹äºº |
| | | const handleAdd = () => { |
| | | const newOrder = approverList.value.length + 1; |
| | | approverList.value.push({ |
| | | id: null, |
| | | approveType: currentApproveType.value, |
| | | nodeOrder: index + 1, |
| | | approverId: item.approverId, |
| | | approverName: item.approverName, |
| | | })); |
| | | await addApproveProcessConfigNode(payload); |
| | | ElMessage.success('ä¿åæå'); |
| | | await loadData(); |
| | | } catch (error) { |
| | | ElMessage.error('ä¿å失败'); |
| | | } finally { |
| | | saveLoading.value = false; |
| | | } |
| | | }; |
| | | approverId: null, |
| | | approverName: "", |
| | | sortOrder: newOrder, |
| | | }); |
| | | }; |
| | | |
| | | // éç½® |
| | | const handleReset = () => { |
| | | if (originalList.value.length === 0) { |
| | | approverList.value = []; |
| | | return; |
| | | } |
| | | ElMessageBox.confirm('ç¡®å®è¦éç½®å½åé
ç½®åï¼æªä¿åçæ´æ¹å°ä¸¢å¤±ã', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | approverList.value = JSON.parse(JSON.stringify(originalList.value)); |
| | | ElMessage.success('å·²éç½®'); |
| | | // å é¤å®¡æ¹äºº |
| | | const handleDelete = index => { |
| | | ElMessageBox.confirm("ç¡®å®å é¤è¯¥å®¡æ¹äººåï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | .then(() => { |
| | | approverList.value.splice(index, 1); |
| | | approverList.value.forEach((item, idx) => { |
| | | item.sortOrder = idx + 1; |
| | | }); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await loadUserList(); |
| | | await loadData(); |
| | | }); |
| | | // åç§» |
| | | const moveLeft = index => { |
| | | if (index === 0) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index - 1]; |
| | | approverList.value[index - 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index - 1].sortOrder = index; |
| | | }; |
| | | |
| | | // åç§» |
| | | const moveRight = index => { |
| | | if (index === approverList.value.length - 1) return; |
| | | const temp = approverList.value[index]; |
| | | approverList.value[index] = approverList.value[index + 1]; |
| | | approverList.value[index + 1] = temp; |
| | | approverList.value[index].sortOrder = index + 1; |
| | | approverList.value[index + 1].sortOrder = index + 2; |
| | | }; |
| | | |
| | | // ä¿åé
ç½® |
| | | const handleSave = async () => { |
| | | if (approverList.value.length === 0) { |
| | | ElMessage.warning("请è³å°é
ç½®ä¸ä¸ªå®¡æ¹äºº"); |
| | | return; |
| | | } |
| | | |
| | | const hasEmptyApprover = approverList.value.some(item => !item.approverId); |
| | | if (hasEmptyApprover) { |
| | | ElMessage.warning("è¯·éæ©ææå®¡æ¹äºº"); |
| | | return; |
| | | } |
| | | |
| | | const approverIds = approverList.value.map(item => item.approverId); |
| | | const uniqueIds = [...new Set(approverIds)]; |
| | | if (uniqueIds.length !== approverIds.length) { |
| | | ElMessage.warning("审æ¹äººä¸è½éå¤"); |
| | | return; |
| | | } |
| | | |
| | | saveLoading.value = true; |
| | | try { |
| | | const payload = approverList.value.map((item, index) => ({ |
| | | approveType: currentApproveType.value, |
| | | nodeOrder: index + 1, |
| | | approverId: item.approverId, |
| | | approverName: item.approverName, |
| | | })); |
| | | await addApproveProcessConfigNode(payload); |
| | | ElMessage.success("ä¿åæå"); |
| | | await loadData(); |
| | | } catch (error) { |
| | | ElMessage.error("ä¿å失败"); |
| | | } finally { |
| | | saveLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // éç½® |
| | | const handleReset = () => { |
| | | if (originalList.value.length === 0) { |
| | | approverList.value = []; |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("ç¡®å®è¦éç½®å½åé
ç½®åï¼æªä¿åçæ´æ¹å°ä¸¢å¤±ã", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | approverList.value = JSON.parse(JSON.stringify(originalList.value)); |
| | | ElMessage.success("å·²éç½®"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await loadUserList(); |
| | | await loadData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | margin-bottom: 6px; |
| | | } |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .title-icon { |
| | | font-size: 24px; |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | .title-icon { |
| | | font-size: 24px; |
| | | color: var(--el-color-primary, #409eff); |
| | | } |
| | | |
| | | .header-desc { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | margin-left: 34px; |
| | | } |
| | | .header-desc { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | margin-left: 34px; |
| | | } |
| | | |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67C23A); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .config-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .type-icon { |
| | | width: 44px; |
| | | height: 44px; |
| | | border-radius: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .flow-wrapper { |
| | | overflow-x: auto; |
| | | padding: 8px 4px; |
| | | } |
| | | |
| | | .flow-container { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 0; |
| | | min-width: min-content; |
| | | } |
| | | |
| | | .flow-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .node-card { |
| | | width: 200px; |
| | | background: var(--el-bg-color, #fff); |
| | | border: 2px solid var(--el-border-color, #e4e7ed); |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .node-card:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .node-card.empty { |
| | | border-style: dashed; |
| | | border-color: var(--el-border-color, #c0c4cc); |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | } |
| | | |
| | | .node-card.empty:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | } |
| | | |
| | | .node-badge { |
| | | position: absolute; |
| | | top: -10px; |
| | | left: 16px; |
| | | width: 24px; |
| | | height: 24px; |
| | | background: var(--el-color-primary, #409EFF); |
| | | color: #fff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .node-avatar-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .node-avatar { |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color, #f0f2f5); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .node-avatar.has-user { |
| | | color: #fff; |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .node-level { |
| | | font-size: 12px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .node-select-section { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .node-select-section :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .node-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding-top: 12px; |
| | | border-top: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .action-btn { |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | .action-btn:hover:not(:disabled) { |
| | | transform: scale(1.1); |
| | | } |
| | | |
| | | .arrow-connector { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 50px; |
| | | position: relative; |
| | | } |
| | | |
| | | .arrow-line { |
| | | flex: 1; |
| | | height: 2px; |
| | | background: var(--el-border-color, #c0c4cc); |
| | | } |
| | | |
| | | .arrow-icon { |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | font-size: 14px; |
| | | margin-left: -2px; |
| | | } |
| | | |
| | | /* æ°å¢èç¹æ ·å¼ */ |
| | | .add-node-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .add-node-card { |
| | | width: 140px; |
| | | height: 200px; |
| | | border: 2px dashed var(--el-border-color, #c0c4cc); |
| | | border-radius: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | flex-shrink: 0; |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .add-node-card:hover { |
| | | border-color: var(--el-color-primary, #409EFF); |
| | | background: var(--el-color-primary-light-9, #f0f7ff); |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | } |
| | | |
| | | .add-icon-wrapper { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 50%; |
| | | background: var(--el-color-primary, #409EFF); |
| | | color: #fff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .add-node-card:hover .add-icon-wrapper { |
| | | transform: scale(1.1); |
| | | box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .add-text { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .add-node-card:hover .add-text { |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | /* ç©ºç¶æ */ |
| | | .empty-state { |
| | | padding: 50px 20px; |
| | | } |
| | | |
| | | .empty-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .empty-icon-wrapper { |
| | | width: 80px; |
| | | height: 80px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .empty-text { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .empty-subtext { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | |
| | | .empty-add-btn { |
| | | margin-top: 8px; |
| | | padding: 12px 28px; |
| | | } |
| | | |
| | | /* åºé¨æç¤º */ |
| | | .bottom-tips { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 12px 20px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .bottom-tips .el-icon { |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-size: 16px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | padding: 3px; |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409eff); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409eff); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67c23a); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .config-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .type-icon { |
| | | width: 44px; |
| | | height: 44px; |
| | | border-radius: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .header-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .header-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .flow-wrapper { |
| | | overflow-x: auto; |
| | | padding: 8px 4px; |
| | | } |
| | | |
| | | .flow-container { |
| | | flex-wrap: wrap; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 0; |
| | | min-width: min-content; |
| | | } |
| | | |
| | | .flow-item { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .node-card { |
| | | width: 200px; |
| | | background: var(--el-bg-color, #fff); |
| | | border: 2px solid var(--el-border-color, #e4e7ed); |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .node-card:hover { |
| | | border-color: var(--el-color-primary, #409eff); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | .node-card.empty { |
| | | border-style: dashed; |
| | | border-color: var(--el-border-color, #c0c4cc); |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | } |
| | | |
| | | .node-card.empty:hover { |
| | | border-color: var(--el-color-primary, #409eff); |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | } |
| | | |
| | | .node-badge { |
| | | position: absolute; |
| | | top: -10px; |
| | | left: 16px; |
| | | width: 24px; |
| | | height: 24px; |
| | | background: var(--el-color-primary, #409eff); |
| | | color: #fff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 13px; |
| | | font-weight: 700; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .node-avatar-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .node-avatar { |
| | | width: 56px; |
| | | height: 56px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color, #f0f2f5); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .node-avatar.has-user { |
| | | color: #fff; |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .node-level { |
| | | font-size: 12px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .node-select-section { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .node-select-section :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .node-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding-top: 12px; |
| | | border-top: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .action-btn { |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | .action-btn:hover:not(:disabled) { |
| | | transform: scale(1.1); |
| | | } |
| | | |
| | | .arrow-connector { |
| | | width: 100%; |
| | | height: 30px; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | display: flex; |
| | | align-items: center; |
| | | width: 50px; |
| | | position: relative; |
| | | } |
| | | |
| | | .arrow-line { |
| | | width: 2px; |
| | | height: 30px; |
| | | flex: 1; |
| | | height: 2px; |
| | | background: var(--el-border-color, #c0c4cc); |
| | | } |
| | | |
| | | .arrow-icon { |
| | | right: auto; |
| | | top: auto; |
| | | bottom: -5px; |
| | | transform: rotate(90deg); |
| | | color: var(--el-text-color-placeholder, #c0c4cc); |
| | | font-size: 14px; |
| | | margin-left: -2px; |
| | | } |
| | | |
| | | /* æ°å¢èç¹æ ·å¼ */ |
| | | .add-node-item { |
| | | width: 100%; |
| | | justify-content: center; |
| | | margin-top: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .add-node-item .arrow-connector { |
| | | display: none; |
| | | .add-node-card { |
| | | width: 140px; |
| | | height: 200px; |
| | | border: 2px dashed var(--el-border-color, #c0c4cc); |
| | | border-radius: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | background: var(--el-fill-color-light, #fafbfc); |
| | | flex-shrink: 0; |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | |
| | | .add-node-card:hover { |
| | | border-color: var(--el-color-primary, #409eff); |
| | | background: var(--el-color-primary-light-9, #f0f7ff); |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15); |
| | | } |
| | | |
| | | .add-icon-wrapper { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 50%; |
| | | background: var(--el-color-primary, #409eff); |
| | | color: #fff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .add-node-card:hover .add-icon-wrapper { |
| | | transform: scale(1.1); |
| | | box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4); |
| | | } |
| | | |
| | | .add-text { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .add-node-card:hover .add-text { |
| | | color: var(--el-color-primary, #409eff); |
| | | } |
| | | |
| | | /* ç©ºç¶æ */ |
| | | .empty-state { |
| | | padding: 50px 20px; |
| | | } |
| | | |
| | | .empty-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .empty-icon-wrapper { |
| | | width: 80px; |
| | | height: 80px; |
| | | border-radius: 50%; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .empty-text { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .empty-subtext { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | |
| | | .empty-add-btn { |
| | | margin-top: 8px; |
| | | padding: 12px 28px; |
| | | } |
| | | |
| | | /* åºé¨æç¤º */ |
| | | .bottom-tips { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 12px 20px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .bottom-tips .el-icon { |
| | | color: var(--el-color-primary, #409eff); |
| | | font-size: 16px; |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .type-tabs { |
| | | padding: 3px; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .flow-container { |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .arrow-connector { |
| | | width: 100%; |
| | | height: 30px; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .arrow-line { |
| | | width: 2px; |
| | | height: 30px; |
| | | } |
| | | |
| | | .arrow-icon { |
| | | right: auto; |
| | | top: auto; |
| | | bottom: -5px; |
| | | transform: rotate(90deg); |
| | | } |
| | | |
| | | .add-node-item { |
| | | width: 100%; |
| | | justify-content: center; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .add-node-item .arrow-connector { |
| | | display: none; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <!-- 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ --> |
| | | <div class="type-tabs"> |
| | | <div |
| | | v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()" |
| | | > |
| | | <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <div v-for="type in approveTypes" |
| | | :key="type.value" |
| | | class="type-tab" |
| | | :class="{ active: activeTab === type.value }" |
| | | @click="activeTab = type.value; handleTabChange()"> |
| | | <el-icon :size="14" |
| | | :style="{ color: activeTab === type.value ? type.color : '#909399' }"> |
| | | <component :is="type.icon" /> |
| | | </el-icon> |
| | | <span class="tab-name">{{ type.label }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æç´¢åæä½åºå --> |
| | | <el-card class="search-card" shadow="never"> |
| | | <el-card class="search-card" |
| | | shadow="never"> |
| | | <div class="search-content"> |
| | | <div class="search-filters"> |
| | | <div class="filter-item"> |
| | | <span class="filter-label">æµç¨ç¼å·</span> |
| | | <el-input |
| | | v-model="searchForm.approveId" |
| | | placeholder="请è¾å
¥æµç¨ç¼å·" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @keyup.enter="handleQuery" |
| | | class="search-input" |
| | | /> |
| | | <el-input v-model="searchForm.approveId" |
| | | placeholder="请è¾å
¥æµç¨ç¼å·" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @keyup.enter="handleQuery" |
| | | class="search-input" /> |
| | | </div> |
| | | <div class="filter-item"> |
| | | <span class="filter-label">审æ¹ç¶æ</span> |
| | | <el-select |
| | | v-model="searchForm.approveStatus" |
| | | clearable |
| | | @change="handleQuery" |
| | | placeholder="è¯·éæ©ç¶æ" |
| | | class="search-select" |
| | | > |
| | | <el-option label="å¾
å®¡æ ¸" :value="0"> |
| | | <el-tag size="small" type="warning">å¾
å®¡æ ¸</el-tag> |
| | | <el-select v-model="searchForm.approveStatus" |
| | | clearable |
| | | @change="handleQuery" |
| | | placeholder="è¯·éæ©ç¶æ" |
| | | class="search-select"> |
| | | <el-option label="å¾
å®¡æ ¸" |
| | | :value="0"> |
| | | <el-tag size="small" |
| | | type="warning">å¾
å®¡æ ¸</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸ä¸" :value="1"> |
| | | <el-tag size="small" type="primary">å®¡æ ¸ä¸</el-tag> |
| | | <el-option label="å®¡æ ¸ä¸" |
| | | :value="1"> |
| | | <el-tag size="small" |
| | | type="primary">å®¡æ ¸ä¸</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸å®æ" :value="2"> |
| | | <el-tag size="small" type="success">å®¡æ ¸å®æ</el-tag> |
| | | <el-option label="å®¡æ ¸å®æ" |
| | | :value="2"> |
| | | <el-tag size="small" |
| | | type="success">å®¡æ ¸å®æ</el-tag> |
| | | </el-option> |
| | | <el-option label="å®¡æ ¸æªéè¿" :value="3"> |
| | | <el-tag size="small" type="danger">å®¡æ ¸æªéè¿</el-tag> |
| | | <el-option label="å®¡æ ¸æªéè¿" |
| | | :value="3"> |
| | | <el-tag size="small" |
| | | type="danger">å®¡æ ¸æªéè¿</el-tag> |
| | | </el-option> |
| | | <el-option label="已鿰æäº¤" :value="4"> |
| | | <el-tag size="small" type="info">已鿰æäº¤</el-tag> |
| | | <el-option label="已鿰æäº¤" |
| | | :value="4"> |
| | | <el-tag size="small" |
| | | type="info">已鿰æäº¤</el-tag> |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | <el-button type="primary" @click="handleQuery" class="search-btn"> |
| | | <el-icon><Search /></el-icon> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | class="search-btn"> |
| | | <el-icon> |
| | | <Search /> |
| | | </el-icon> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetQuery" class="reset-btn"> |
| | | <el-icon><RefreshRight /></el-icon> |
| | | <el-button @click="resetQuery" |
| | | class="reset-btn"> |
| | | <el-icon> |
| | | <RefreshRight /> |
| | | </el-icon> |
| | | éç½® |
| | | </el-button> |
| | | </div> |
| | | <div class="search-actions"> |
| | | <el-button |
| | | type="primary" |
| | | @click="openForm('add')" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn primary" |
| | | > |
| | | <el-icon><Plus /></el-icon> |
| | | <el-button type="primary" |
| | | @click="openForm('add')" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn primary"> |
| | | <el-icon> |
| | | <Plus /> |
| | | </el-icon> |
| | | æ°å¢ |
| | | </el-button> |
| | | <el-button @click="handleOut" class="action-btn"> |
| | | <el-icon><Download /></el-icon> |
| | | <el-button @click="handleOut" |
| | | class="action-btn"> |
| | | <el-icon> |
| | | <Download /> |
| | | </el-icon> |
| | | å¯¼åº |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | plain |
| | | @click="handleDelete" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn danger" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete" |
| | | v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" |
| | | class="action-btn danger"> |
| | | <el-icon> |
| | | <Delete /> |
| | | </el-icon> |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- æ°æ®è¡¨æ ¼ --> |
| | | <el-card class="table-card" shadow="never" v-loading="tableLoading"> |
| | | <el-card class="table-card" |
| | | shadow="never" |
| | | v-loading="tableLoading"> |
| | | <template #header> |
| | | <div class="table-header"> |
| | | <div class="table-title"> |
| | | <div class="type-tag" :style="{ backgroundColor: currentTypeInfo.color }"> |
| | | <el-icon color="#fff" :size="16"><component :is="currentTypeInfo.icon" /></el-icon> |
| | | <div class="type-tag" |
| | | :style="{ backgroundColor: currentTypeInfo.color }"> |
| | | <el-icon color="#fff" |
| | | :size="16"> |
| | | <component :is="currentTypeInfo.icon" /> |
| | | </el-icon> |
| | | </div> |
| | | <span>{{ currentTypeInfo.label }}å表</span> |
| | | <el-tag type="info" size="small" effect="plain" class="count-tag"> |
| | | <el-tag type="info" |
| | | size="small" |
| | | effect="plain" |
| | | class="count-tag"> |
| | | å
± {{ page.total }} æ¡ |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumnCopy" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | class="custom-table" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumnCopy" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | class="custom-table"></PIMTable> |
| | | </el-card> |
| | | |
| | | <!-- å¼¹çªç»ä»¶ --> |
| | | <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia> |
| | | <info-form-dia ref="infoFormDia" |
| | | @close="handleQuery" |
| | | :approveType="currentApproveType"></info-form-dia> |
| | | <approval-dia ref="approvalDia" |
| | | @close="handleQuery" |
| | | :approveType="currentApproveType"></approval-dia> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | record-type="approve_process" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search, Plus, Delete, Download, RefreshRight, DocumentChecked } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref, computed, reactive, toRefs, nextTick, getCurrentInstance, defineAsyncComponent} from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { useRoute } from 'vue-router'; |
| | | import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | import { |
| | | Search, |
| | | Plus, |
| | | Delete, |
| | | Download, |
| | | RefreshRight, |
| | | DocumentChecked, |
| | | } from "@element-plus/icons-vue"; |
| | | import { |
| | | onMounted, |
| | | ref, |
| | | computed, |
| | | reactive, |
| | | toRefs, |
| | | nextTick, |
| | | getCurrentInstance, |
| | | defineAsyncComponent, |
| | | } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { useRoute } from "vue-router"; |
| | | import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue"; |
| | | import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue"; |
| | | import { |
| | | approveProcessDelete, |
| | | approveProcessListPage, |
| | | } from "@/api/collaborativeApproval/approvalProcess.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | const userStore = useUserStore(); |
| | | const route = useRoute(); |
| | | |
| | | // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºå
¬åºç®¡ç |
| | | const activeTab = ref('1'); |
| | | // å½åéä¸çæ ç¾é¡µï¼é»è®¤ä¸ºè¯·å管ç |
| | | const activeTab = ref("2"); |
| | | |
| | | // åç±»åæ°éç»è®¡ |
| | | const typeCounts = ref({}); |
| | | // åç±»åæ°éç»è®¡ |
| | | const typeCounts = ref({}); |
| | | |
| | | // 审æ¹ç±»åé
ç½® |
| | | const approveTypes = [ |
| | | { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: '2', label: '请å管ç', icon: 'Calendar', color: '#67C23A' }, |
| | | { value: '3', label: 'åºå·®ç®¡ç', icon: 'Location', color: '#E6A23C' }, |
| | | { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | // 审æ¹ç±»åé
ç½® |
| | | const approveTypes = [ |
| | | // { value: '1', label: 'å
¬åºç®¡ç', icon: 'Suitcase', color: '#409EFF' }, |
| | | { value: "2", label: "请å管ç", icon: "Calendar", color: "#67C23A" }, |
| | | { value: "3", label: "åºå·®ç®¡ç", icon: "Location", color: "#E6A23C" }, |
| | | // { value: '4', label: 'æ¥é管ç', icon: 'Money', color: '#F56C6C' }, |
| | | // { value: '5', label: 'éè´å®¡æ¹', icon: 'ShoppingCart', color: '#909399' }, |
| | | // { value: '6', label: 'æ¥ä»·å®¡æ¹', icon: 'DocumentChecked', color: '#9B59B6' }, |
| | | // { value: '7', label: 'å货审æ¹', icon: 'Van', color: '#1ABC9C' }, |
| | | ]; |
| | | |
| | | // å½å审æ¹ç±»åä¿¡æ¯ |
| | | const currentTypeInfo = computed(() => { |
| | | return approveTypes.find(t => t.value === activeTab.value) || approveTypes[0]; |
| | | }); |
| | | // å½å审æ¹ç±»åä¿¡æ¯ |
| | | const currentTypeInfo = computed(() => { |
| | | return approveTypes.find(t => t.value === activeTab.value) || approveTypes[0]; |
| | | }); |
| | | |
| | | // è·åç±»åæ°é |
| | | const getTypeCount = (value) => { |
| | | return typeCounts.value[value] || 0; |
| | | }; |
| | | // è·åç±»åæ°é |
| | | const getTypeCount = value => { |
| | | return typeCounts.value[value] || 0; |
| | | }; |
| | | |
| | | // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | // å½å审æ¹ç±»åï¼æ ¹æ®éä¸çæ ç¾é¡µè®¡ç® |
| | | const currentApproveType = computed(() => { |
| | | return Number(activeTab.value); |
| | | }); |
| | | |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® |
| | | searchForm.value.approveId = ''; |
| | | searchForm.value.approveStatus = ''; |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | // 忢æ ç¾é¡µæ¶éç½®æç´¢æ¡ä»¶åå页ï¼å¹¶éæ°å è½½æ°æ® |
| | | searchForm.value.approveId = ""; |
| | | searchForm.value.approveStatus = ""; |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | approveId: "", |
| | | approveStatus: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | approveId: "", |
| | | approveStatus: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | // éç½®æç´¢ |
| | | const resetQuery = () => { |
| | | searchForm.value.approveId = ""; |
| | | searchForm.value.approveStatus = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // éç½®æç´¢ |
| | | const resetQuery = () => { |
| | | searchForm.value.approveId = ''; |
| | | searchForm.value.approveStatus = ''; |
| | | handleQuery(); |
| | | }; |
| | | // å¨æè¡¨æ ¼åé
ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå |
| | | const tableColumnCopy = computed(() => { |
| | | const isLeaveType = currentApproveType.value === 2; // 请å管ç |
| | | const isBusinessTripType = currentApproveType.value === 3; // åºå·®ç®¡ç |
| | | const isReimburseType = currentApproveType.value === 4; // æ¥é管ç |
| | | const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ |
| | | const isPurchaseType = currentApproveType.value === 5; // éè´å®¡æ¹ |
| | | |
| | | // å¨æè¡¨æ ¼åé
ç½®ï¼æ ¹æ®å®¡æ¹ç±»åçæå |
| | | const tableColumnCopy = computed(() => { |
| | | const isLeaveType = currentApproveType.value === 2; // 请å管ç |
| | | const isBusinessTripType = currentApproveType.value === 3; // åºå·®ç®¡ç |
| | | const isReimburseType = currentApproveType.value === 4; // æ¥é管ç |
| | | const isQuotationType = currentApproveType.value === 6; // æ¥ä»·å®¡æ¹ |
| | | const isPurchaseType = currentApproveType.value === 5; // éè´å®¡æ¹ |
| | | |
| | | // åºç¡åé
ç½® |
| | | const baseColumns = [ |
| | | { |
| | | label: "审æ¹ç¶æ", |
| | | prop: "approveStatus", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "å¾
å®¡æ ¸"; |
| | | } else if (params == 1) { |
| | | return "å®¡æ ¸ä¸"; |
| | | } else if (params == 2) { |
| | | return "å®¡æ ¸å®æ"; |
| | | } else if (params == 4) { |
| | | return "已鿰æäº¤"; |
| | | } else { |
| | | return 'ä¸éè¿'; |
| | | } |
| | | // åºç¡åé
ç½® |
| | | const baseColumns = [ |
| | | { |
| | | label: "审æ¹ç¶æ", |
| | | prop: "approveStatus", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: params => { |
| | | if (params == 0) { |
| | | return "å¾
å®¡æ ¸"; |
| | | } else if (params == 1) { |
| | | return "å®¡æ ¸ä¸"; |
| | | } else if (params == 2) { |
| | | return "å®¡æ ¸å®æ"; |
| | | } else if (params == 4) { |
| | | return "已鿰æäº¤"; |
| | | } else { |
| | | return "ä¸éè¿"; |
| | | } |
| | | }, |
| | | formatType: params => { |
| | | if (params == 0) { |
| | | return "warning"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else if (params == 2) { |
| | | return "success"; |
| | | } else if (params == 4) { |
| | | return "info"; |
| | | } else { |
| | | return "danger"; |
| | | } |
| | | }, |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "warning"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else if (params == 2) { |
| | | return "success"; |
| | | } else if (params == 4) { |
| | | return "info"; |
| | | } else { |
| | | return 'danger'; |
| | | } |
| | | { |
| | | label: "æµç¨ç¼å·", |
| | | prop: "approveId", |
| | | width: 170, |
| | | }, |
| | | }, |
| | | { |
| | | label: "æµç¨ç¼å·", |
| | | prop: "approveId", |
| | | width: 170 |
| | | }, |
| | | { |
| | | label: "ç³è¯·é¨é¨", |
| | | prop: "approveDeptName", |
| | | width: 220 |
| | | }, |
| | | { |
| | | label: isQuotationType ? "æ¥ä»·åå·" : isPurchaseType ? "éè´ååå·" : "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | }, |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | | width: 120 |
| | | { |
| | | label: "ç³è¯·é¨é¨", |
| | | prop: "approveDeptName", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: isQuotationType |
| | | ? "æ¥ä»·åå·" |
| | | : isPurchaseType |
| | | ? "éè´ååå·" |
| | | : "审æ¹äºç±", |
| | | prop: "approveReason", |
| | | }, |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "approveUserName", |
| | | width: 120, |
| | | }, |
| | | ]; |
| | | |
| | | // éé¢åï¼ä»
æ¥éç®¡çæ¾ç¤ºï¼ |
| | | if (isReimburseType) { |
| | | baseColumns.push({ |
| | | label: "éé¢ï¼å
ï¼", |
| | | prop: "price", |
| | | width: 120, |
| | | }); |
| | | } |
| | | ]; |
| | | |
| | | // éé¢åï¼ä»
æ¥éç®¡çæ¾ç¤ºï¼ |
| | | if (isReimburseType) { |
| | | |
| | | // 请å管çï¼å¼å§æ¥æ / ç»ææ¥æ |
| | | if (isLeaveType) { |
| | | baseColumns.push( |
| | | { label: "å¼å§æ¥æ", prop: "startDate", width: 120 }, |
| | | { label: "ç»ææ¥æ", prop: "endDate", width: 120 } |
| | | ); |
| | | } |
| | | |
| | | // åºå·®ç®¡çï¼å¼å§æ¶é´ / ç»ææ¶é´ï¼ä¸å«ç§ï¼ |
| | | if (isBusinessTripType) { |
| | | baseColumns.push( |
| | | { |
| | | label: "å¼å§æ¶é´", |
| | | prop: "startDateTime", |
| | | width: 180, |
| | | formatData: val => (val ? val.substring(0, 16) : ""), |
| | | }, |
| | | { |
| | | label: "ç»ææ¶é´", |
| | | prop: "endDateTime", |
| | | width: 180, |
| | | formatData: val => (val ? val.substring(0, 16) : ""), |
| | | } |
| | | ); |
| | | } |
| | | |
| | | // å½å审æ¹äººå |
| | | baseColumns.push({ |
| | | label: "éé¢ï¼å
ï¼", |
| | | prop: "price", |
| | | width: 120 |
| | | label: "å½å审æ¹äºº", |
| | | prop: "approveUserCurrentName", |
| | | width: 120, |
| | | }); |
| | | } |
| | | |
| | | // 请å管çï¼å¼å§æ¥æ / ç»ææ¥æ |
| | | if (isLeaveType) { |
| | | baseColumns.push( |
| | | { label: "å¼å§æ¥æ", prop: "startDate", width: 120 }, |
| | | { label: "ç»ææ¥æ", prop: "endDate", width: 120 } |
| | | ); |
| | | } |
| | | |
| | | // åºå·®ç®¡çï¼å¼å§æ¶é´ / ç»ææ¶é´ï¼ä¸å«ç§ï¼ |
| | | if (isBusinessTripType) { |
| | | baseColumns.push( |
| | | // ç³è¯·æ¶é´ - ææç±»å齿¾ç¤º |
| | | baseColumns.push({ |
| | | label: "ç³è¯·æ¶é´", |
| | | prop: "approveTime", |
| | | width: 180, |
| | | }); |
| | | |
| | | // å®¡æ¹æ¶é´ - ææç±»å齿¾ç¤º |
| | | baseColumns.push({ |
| | | label: "å®¡æ¹æ¶é´", |
| | | prop: "approveOverTime", |
| | | width: 180, |
| | | }); |
| | | |
| | | // æä½å |
| | | const actionOperations = [ |
| | | { |
| | | label: "å¼å§æ¶é´", |
| | | prop: "startDateTime", |
| | | width: 180, |
| | | formatData: (val) => val ? val.substring(0, 16) : '' |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: row => |
| | | currentApproveType.value === 5 || |
| | | currentApproveType.value === 6 || |
| | | currentApproveType.value === 7 || |
| | | row.approveStatus == 2 || |
| | | row.approveStatus == 1 || |
| | | row.approveStatus == 4, |
| | | }, |
| | | { |
| | | label: "ç»ææ¶é´", |
| | | prop: "endDateTime", |
| | | width: 180, |
| | | formatData: (val) => val ? val.substring(0, 16) : '' |
| | | } |
| | | ); |
| | | } |
| | | |
| | | // å½å审æ¹äººå |
| | | baseColumns.push({ |
| | | label: "å½å审æ¹äºº", |
| | | prop: "approveUserCurrentName", |
| | | width: 120 |
| | | }); |
| | | name: "å®¡æ ¸", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openApprovalDia("approval", row); |
| | | }, |
| | | disabled: row => |
| | | row.approveUserCurrentId == null || |
| | | row.approveStatus == 2 || |
| | | row.approveStatus == 3 || |
| | | row.approveStatus == 4 || |
| | | row.approveUserCurrentId !== userStore.id, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openApprovalDia("view", row); |
| | | }, |
| | | }, |
| | | ]; |
| | | |
| | | // ç³è¯·æ¶é´ - ææç±»å齿¾ç¤º |
| | | baseColumns.push({ |
| | | label: "ç³è¯·æ¶é´", |
| | | prop: "approveTime", |
| | | width: 180, |
| | | }); |
| | | // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤º"éä»¶"æä½ |
| | | if (!isQuotationType) { |
| | | actionOperations.push({ |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | // å®¡æ¹æ¶é´ - ææç±»å齿¾ç¤º |
| | | baseColumns.push({ |
| | | label: "å®¡æ¹æ¶é´", |
| | | prop: "approveOverTime", |
| | | width: 180, |
| | | }); |
| | | |
| | | // æä½å |
| | | const actionOperations = [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | disabled: (row) => |
| | | currentApproveType.value === 5 || |
| | | currentApproveType.value === 6 || |
| | | currentApproveType.value === 7 || |
| | | row.approveStatus == 2 || |
| | | row.approveStatus == 1 || |
| | | row.approveStatus == 4 |
| | | }, |
| | | { |
| | | name: "å®¡æ ¸", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openApprovalDia("approval", row); |
| | | }, |
| | | disabled: (row) => |
| | | row.approveUserCurrentId == null || |
| | | row.approveStatus == 2 || |
| | | row.approveStatus == 3 || |
| | | row.approveStatus == 4 || |
| | | row.approveUserCurrentId !== userStore.id |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openApprovalDia("view", row); |
| | | }, |
| | | }, |
| | | ]; |
| | | |
| | | // æ¥ä»·å®¡æ¹ï¼ç±»å 6ï¼ä¸å±ç¤º"éä»¶"æä½ |
| | | if (!isQuotationType) { |
| | | actionOperations.push({ |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesFormDia(row); |
| | | }, |
| | | baseColumns.push({ |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 230, |
| | | operation: actionOperations, |
| | | }); |
| | | } |
| | | |
| | | baseColumns.push({ |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 230, |
| | | operation: actionOperations, |
| | | return baseColumns; |
| | | }); |
| | | |
| | | return baseColumns; |
| | | }); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0 |
| | | }); |
| | | const infoFormDia = ref() |
| | | const approvalDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const infoFormDia = ref(); |
| | | const approvalDia = ref(); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId =ref(0) |
| | | const fileDialogVisible = ref(false) |
| | | // æå¼éä»¶å¼¹çª |
| | | const recordId = ref(0); |
| | | const fileDialogVisible = ref(false); |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async (row) => { |
| | | recordId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = async row => { |
| | | recordId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | approveProcessListPage({...page, ...searchForm.value, approveType: currentApproveType.value}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | // æ´æ°å½åç±»åæ°é |
| | | typeCounts.value[activeTab.value] = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | const type = currentApproveType.value |
| | | const urlMap = { |
| | | 0: "/approveProcess/exportZero", |
| | | 1: "/approveProcess/exportOne", |
| | | 2: "/approveProcess/exportTwo", |
| | | 3: "/approveProcess/exportThree", |
| | | 4: "/approveProcess/exportFour", |
| | | 5: "/approveProcess/exportFive", |
| | | 6: "/approveProcess/exportSix", |
| | | 7: "/approveProcess/exportSeven", |
| | | } |
| | | const url = urlMap[type] || urlMap[0] |
| | | const nameMap = { |
| | | 0: "åå审æ¹ç®¡ç表", |
| | | 1: "å
¬åºç®¡ç审æ¹è¡¨", |
| | | 2: "请å管ç审æ¹è¡¨", |
| | | 3: "åºå·®ç®¡ç审æ¹è¡¨", |
| | | 4: "æ¥é管ç审æ¹è¡¨", |
| | | 5: "éè´ç³è¯·å®¡æ¹è¡¨", |
| | | 6: "æ¥ä»·å®¡æ¹è¡¨", |
| | | 7: "å货审æ¹è¡¨", |
| | | } |
| | | const fileName = nameMap[type] || nameMap[0] |
| | | proxy.download(url, {}, `${fileName}.xlsx`) |
| | | } |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | approveProcessListPage({ |
| | | ...page, |
| | | ...searchForm.value, |
| | | approveType: currentApproveType.value, |
| | | }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | // æ´æ°å½åç±»åæ°é |
| | | typeCounts.value[activeTab.value] = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | const type = currentApproveType.value; |
| | | const urlMap = { |
| | | 0: "/approveProcess/exportZero", |
| | | 1: "/approveProcess/exportOne", |
| | | 2: "/approveProcess/exportTwo", |
| | | 3: "/approveProcess/exportThree", |
| | | 4: "/approveProcess/exportFour", |
| | | 5: "/approveProcess/exportFive", |
| | | 6: "/approveProcess/exportSix", |
| | | 7: "/approveProcess/exportSeven", |
| | | }; |
| | | const url = urlMap[type] || urlMap[0]; |
| | | const nameMap = { |
| | | 0: "åå审æ¹ç®¡ç表", |
| | | 1: "å
¬åºç®¡ç审æ¹è¡¨", |
| | | 2: "请å管ç审æ¹è¡¨", |
| | | 3: "åºå·®ç®¡ç审æ¹è¡¨", |
| | | 4: "æ¥é管ç审æ¹è¡¨", |
| | | 5: "éè´ç³è¯·å®¡æ¹è¡¨", |
| | | 6: "æ¥ä»·å®¡æ¹è¡¨", |
| | | 7: "å货审æ¹è¡¨", |
| | | }; |
| | | const fileName = nameMap[type] || nameMap[0]; |
| | | proxy.download(url, {}, `${fileName}.xlsx`); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼æ°å¢ãç¼è¾å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | infoFormDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openApprovalDia = (type, row) => { |
| | | nextTick(() => { |
| | | approvalDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // æå¼æ°å¢ãç¼è¾å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | infoFormDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // æå¼æ°å¢æ£éªå¼¹æ¡ |
| | | const openApprovalDia = (type, row) => { |
| | | nextTick(() => { |
| | | approvalDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.approveId); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.approveId); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | approveProcessDelete(ids).then((res) => { |
| | | approveProcessDelete(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | // æ ¹æ®URLåæ°è®¾ç½®æ ç¾é¡µåæ¥è¯¢æ¡ä»¶ |
| | | const approveType = route.query.approveType; |
| | | const approveId = route.query.approveId; |
| | | |
| | | if (approveType) { |
| | | // 设置æ ç¾é¡µï¼approveType å¯¹åº activeTab ç nameï¼ |
| | | activeTab.value = String(approveType); |
| | | } |
| | | |
| | | if (approveId) { |
| | | // 设置æµç¨ç¼å·æ¥è¯¢æ¡ä»¶ |
| | | searchForm.value.approveId = String(approveId); |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | getList(); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | // æ ¹æ®URLåæ°è®¾ç½®æ ç¾é¡µåæ¥è¯¢æ¡ä»¶ |
| | | const approveType = route.query.approveType; |
| | | const approveId = route.query.approveId; |
| | | |
| | | if (approveType) { |
| | | // 设置æ ç¾é¡µï¼approveType å¯¹åº activeTab ç nameï¼ |
| | | activeTab.value = String(approveType); |
| | | } |
| | | |
| | | if (approveId) { |
| | | // 设置æµç¨ç¼å·æ¥è¯¢æ¡ä»¶ |
| | | searchForm.value.approveId = String(approveId); |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | .page-header { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | .header-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .title-icon { |
| | | font-size: 28px; |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | .title-icon { |
| | | font-size: 28px; |
| | | color: var(--el-color-primary, #409eff); |
| | | } |
| | | |
| | | .header-text { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .main-title { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .sub-title { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409EFF); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409EFF); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67C23A); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* æç´¢å¡ç */ |
| | | .search-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .search-filters { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .filter-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .filter-label { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .search-input, |
| | | .search-select { |
| | | width: 200px; |
| | | } |
| | | |
| | | .search-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .reset-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .action-btn.primary { |
| | | background: var(--el-color-primary, #409EFF); |
| | | border: none; |
| | | } |
| | | |
| | | .action-btn.danger { |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .action-btn.danger:hover { |
| | | background: #f56c6c; |
| | | color: #fff; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å¡ç */ |
| | | .table-card { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .table-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .type-tag { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .count-tag { |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | .custom-table { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | /* ååºå¼ */ |
| | | @media (max-width: 1200px) { |
| | | .search-content { |
| | | .header-text { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .search-filters { |
| | | justify-content: flex-start; |
| | | .main-title { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .search-actions { |
| | | justify-content: flex-end; |
| | | .sub-title { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-secondary, #909399); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | /* 审æ¹ç±»å忢 - ç´§åæ ç¾å¼ */ |
| | | .type-tabs { |
| | | padding: 3px; |
| | | display: flex; |
| | | gap: 4px; |
| | | margin-bottom: 16px; |
| | | padding: 4px; |
| | | background: var(--el-fill-color-light, #f5f7fa); |
| | | border-radius: 8px; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 8px 14px; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | white-space: nowrap; |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | } |
| | | |
| | | .type-tab:hover { |
| | | background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1)); |
| | | color: var(--el-color-primary, #409eff); |
| | | } |
| | | |
| | | .type-tab.active { |
| | | background: var(--el-bg-color, #fff); |
| | | color: var(--el-color-primary, #409eff); |
| | | font-weight: 600; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .tab-count { |
| | | min-width: 16px; |
| | | height: 16px; |
| | | padding: 0 5px; |
| | | background: var(--el-color-success, #67c23a); |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | /* æç´¢å¡ç */ |
| | | .search-card { |
| | | margin-bottom: 16px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .search-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .search-filters { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .filter-item { |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .filter-label { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular, #606266); |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .search-input, |
| | | .search-select { |
| | | width: 100%; |
| | | width: 200px; |
| | | } |
| | | } |
| | | |
| | | .search-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .reset-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .search-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .action-btn.primary { |
| | | background: var(--el-color-primary, #409eff); |
| | | border: none; |
| | | } |
| | | |
| | | .action-btn.danger { |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .action-btn.danger:hover { |
| | | background: #f56c6c; |
| | | color: #fff; |
| | | } |
| | | |
| | | /* è¡¨æ ¼å¡ç */ |
| | | .table-card { |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid var(--el-border-color-light, #ebeef5); |
| | | } |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .table-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary, #303133); |
| | | } |
| | | |
| | | .type-tag { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .count-tag { |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | .custom-table { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | /* ååºå¼ */ |
| | | @media (max-width: 1200px) { |
| | | .search-content { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .search-filters { |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .search-actions { |
| | | justify-content: flex-end; |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .type-tabs { |
| | | padding: 3px; |
| | | } |
| | | |
| | | .type-tab { |
| | | padding: 6px 10px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .tab-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .search-filters { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .filter-item { |
| | | width: 100%; |
| | | } |
| | | |
| | | .search-input, |
| | | .search-select { |
| | | width: 100%; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-alert title="请ä¸ä¼ æè¿ä¸ä¸ªå¹´åº¦çè´¢å¡æ¥è¡¨ï¼ä¸ä¼ åå¯éæ¶éæ°ä¸ä¼ è¦çãæäº¤åå¯å¨æ¬é¡µå®æ¶æ¥ç审æ¹ç»æï¼è¢«éåæ¶è¯·æéååå ä¿®æ¹åéæ°æäº¤ã" |
| | | type="info" |
| | | show-icon |
| | | :closable="false" /> |
| | | <div class="year-cards"> |
| | | <el-card v-for="item in yearRows" |
| | | :key="item.reportYear" |
| | | shadow="never" |
| | | class="year-card"> |
| | | <div class="year-header"> |
| | | <span class="year-title">{{ item.reportYear }} 年度</span> |
| | | <span v-if="item.id"> |
| | | <el-tag type="success" |
| | | size="small" |
| | | v-if="item.auditStatus === 1">å·²éè¿</el-tag> |
| | | <el-tag type="danger" |
| | | size="small" |
| | | v-else-if="item.auditStatus === 2">å·²éå</el-tag> |
| | | <el-tag type="warning" |
| | | size="small" |
| | | v-else>å¾
审æ¹</el-tag> |
| | | </span> |
| | | <el-tag v-else |
| | | type="info" |
| | | size="small">æªä¸ä¼ </el-tag> |
| | | </div> |
| | | <el-alert v-if="item.auditStatus === 2 && item.auditRemark" |
| | | :title="'éååå ï¼' + item.auditRemark" |
| | | type="error" |
| | | show-icon |
| | | :closable="false" |
| | | class="reject-tip" /> |
| | | <div class="year-body"> |
| | | <div class="year-row"> |
| | | <span class="year-label">æ¥è¡¨åç§°</span> |
| | | <el-input v-model="item.reportName" |
| | | :placeholder="'ä¾å¦ï¼' + item.reportYear + 'å¹´åº¦è´¢å¡æ¥è¡¨'" |
| | | clearable /> |
| | | </div> |
| | | <div class="year-row"> |
| | | <span class="year-label">æ¥è¡¨éä»¶</span> |
| | | <FileUpload v-model="item.reportFiles" |
| | | :limit="5" |
| | | :file-size="20" |
| | | :file-type="['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png', 'zip', 'rar']" /> |
| | | </div> |
| | | <div class="year-footer"> |
| | | <span v-if="item.updateTime" |
| | | class="year-tip">æè¿ä¸ä¼ ï¼{{ item.updateTime }}</span> |
| | | <el-button type="primary" |
| | | :loading="savingYear === item.reportYear" |
| | | @click="save(item)">ä¿åä¸ä¼ </el-button> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, getCurrentInstance } from "vue"; |
| | | import FileUpload from "@/components/FileUpload/index.vue"; |
| | | import { listMyFinancialReport, saveCustomerFinancialReport } from "@/api/basicData/customerFinancialReport"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const nowYear = new Date().getFullYear(); |
| | | const yearRows = ref([]); |
| | | const savingYear = ref(0); |
| | | |
| | | // æè¿ä¸å¹´ï¼æªä¸ä¼ ç年度ç空å¾
ä¸ä¼ |
| | | const buildYears = rows => { |
| | | const map = new Map(rows.map(item => [item.reportYear, item])); |
| | | return [nowYear, nowYear - 1, nowYear - 2].map(year => { |
| | | const row = map.get(year); |
| | | return row |
| | | ? { ...row } |
| | | : { reportYear: year, reportName: "", reportFiles: "" }; |
| | | }); |
| | | }; |
| | | |
| | | const load = () => { |
| | | listMyFinancialReport().then(res => { |
| | | yearRows.value = buildYears(res.data || []); |
| | | }); |
| | | }; |
| | | |
| | | const save = item => { |
| | | if (!item.reportFiles) { |
| | | proxy.$modal.msgWarning("请ä¸ä¼ è´¢å¡æ¥è¡¨éä»¶"); |
| | | return; |
| | | } |
| | | savingYear.value = item.reportYear; |
| | | saveCustomerFinancialReport({ |
| | | reportYear: item.reportYear, |
| | | reportName: item.reportName, |
| | | reportFiles: item.reportFiles, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | load(); |
| | | }).finally(() => { |
| | | savingYear.value = 0; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(load); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .year-cards { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .year-card { |
| | | flex: 1 1 320px; |
| | | min-width: 320px; |
| | | } |
| | | |
| | | .reject-tip { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .year-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .year-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .year-row { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .year-label { |
| | | width: 80px; |
| | | flex: 0 0 80px; |
| | | color: #606266; |
| | | line-height: 32px; |
| | | } |
| | | |
| | | .year-row > :not(.year-label) { |
| | | flex: 1; |
| | | } |
| | | |
| | | .year-footer { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .year-tip { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-alert title="请æå¹´åº¦æäº¤æè¿ä¸å¹´çè´¢å¡æ¥åï¼å·²åå¸çç³æ¥ç»æå¯ç´æ¥æ¥çåä¸è½½ã" type="info" show-icon :closable="false" /> |
| | | <el-form :inline="true" :model="query" class="query-form"> |
| | | <el-form-item label="年度"><el-select v-model="query.reportYear" clearable style="width: 140px"><el-option v-for="year in years" :key="year" :label="year" :value="year" /></el-select></el-form-item> |
| | | <el-form-item label="ç¶æ"><el-select v-model="query.status" clearable style="width: 140px"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> |
| | | <el-form-item><el-button type="primary" @click="load">æ¥è¯¢</el-button><el-button type="success" @click="openSubmit">æäº¤ææ</el-button></el-form-item> |
| | | </el-form> |
| | | <el-table v-loading="loading" :data="rows" border> |
| | | <el-table-column prop="reportYear" label="æ¥å年度" width="110" align="center" /> |
| | | <el-table-column prop="reportName" label="ææåç§°" min-width="180" /> |
| | | <el-table-column label="ææéä»¶" min-width="200"><template #default="scope"><FileUpload v-model="scope.row.materialFiles" disabled /></template></el-table-column> |
| | | <el-table-column label="ç¶æ" width="110" align="center"><template #default="scope"><el-tag :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag></template></el-table-column> |
| | | <el-table-column prop="reviewRemark" label="å®¡æ ¸æè§" min-width="180" show-overflow-tooltip /> |
| | | <el-table-column prop="resultText" label="åå¸ç»æ" min-width="220" show-overflow-tooltip /> |
| | | <el-table-column label="ç»æéä»¶" min-width="200"><template #default="scope"><FileUpload v-if="scope.row.resultFiles" v-model="scope.row.resultFiles" disabled /><span v-else>-</span></template></el-table-column> |
| | | <el-table-column prop="publishTime" label="å叿¶é´" width="170" /> |
| | | <el-table-column label="æä½" width="110" fixed="right"><template #default="scope"><el-button v-if="[0, 3].includes(scope.row.status)" link type="primary" @click="openSubmit(scope.row)">{{ scope.row.status === 3 ? "éæ°æäº¤" : "ä¿®æ¹" }}</el-button><span v-else>-</span></template></el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" v-model:page="query.current" v-model:limit="query.size" @pagination="load" /> |
| | | |
| | | <el-dialog v-model="visible" title="æäº¤å¹´åº¦è´¢å¡ææ" width="620px" append-to-body destroy-on-close :close-on-click-modal="false" :before-close="beforeClose" @closed="resetDialog"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="æ¥å年度" prop="reportYear"><el-select v-model="form.reportYear" style="width:100%"><el-option v-for="year in years" :key="year" :label="year" :value="year" /></el-select></el-form-item> |
| | | <el-form-item label="ææåç§°" prop="reportName"><el-input v-model="form.reportName" placeholder="ä¾å¦ï¼2025年度审计æ¥å" /></el-form-item> |
| | | <el-form-item label="ææéä»¶" prop="materialFiles"><FileUpload v-model="form.materialFiles" :limit="8" :file-size="20" /></el-form-item> |
| | | </el-form> |
| | | <template #footer><el-button :disabled="saving" @click="closeDialog">åæ¶</el-button><el-button type="primary" :loading="saving" @click="save">确认æäº¤</el-button></template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FileUpload from "@/components/FileUpload/index.vue"; |
| | | import { listMySubmissions, submitCustomerMaterial } from "@/api/customerPortal/submission"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const nowYear = new Date().getFullYear(); |
| | | const years = [nowYear, nowYear - 1, nowYear - 2]; |
| | | const statusOptions = [{ label: "å¾
å®¡æ ¸", value: 0 }, { label: "å¤çä¸", value: 1 }, { label: "å·²åå¸", value: 2 }, { label: "å·²éå", value: 3 }]; |
| | | const query = reactive({ current: 1, size: 10, reportYear: undefined, status: undefined }); |
| | | const rows = ref([]); const total = ref(0); const loading = ref(false); const visible = ref(false); const saving = ref(false); const formRef = ref(); |
| | | const form = reactive({ reportYear: nowYear, reportName: "", materialFiles: "" }); |
| | | const initialForm = ref(""); |
| | | const rules = { reportYear: [{ required: true, message: "è¯·éæ©æ¥å年度", trigger: "change" }], reportName: [{ required: true, message: "请è¾å
¥ææåç§°", trigger: "blur" }], materialFiles: [{ required: true, message: "请ä¸ä¼ ææéä»¶", trigger: "change" }] }; |
| | | const statusText = (value) => statusOptions.find(item => item.value === value)?.label || "æªç¥"; |
| | | const statusType = (value) => ({ 0: "warning", 1: "primary", 2: "success", 3: "danger" }[value] || "info"); |
| | | async function load() { loading.value = true; try { const res = await listMySubmissions(query); rows.value = res.data?.records || []; total.value = Number(res.data?.total || 0); } finally { loading.value = false; } } |
| | | function formSnapshot() { return JSON.stringify({ reportYear: form.reportYear, reportName: form.reportName, materialFiles: form.materialFiles }); } |
| | | function openSubmit(row) { Object.assign(form, { reportYear: row?.reportYear || nowYear, reportName: row?.reportName || "", materialFiles: row?.materialFiles || "" }); initialForm.value = formSnapshot(); visible.value = true; nextTick(() => formRef.value?.clearValidate()); } |
| | | function resetDialog() { formRef.value?.clearValidate(); Object.assign(form, { reportYear: nowYear, reportName: "", materialFiles: "" }); initialForm.value = ""; } |
| | | function beforeClose(done) { if (saving.value) return; if (formSnapshot() === initialForm.value) return done(); proxy.$modal.confirm("å½åå¡«åå
容尿ªæäº¤ï¼ç¡®å®å
³éåï¼").then(done).catch(() => {}); } |
| | | function closeDialog() { beforeClose(() => { visible.value = false; }); } |
| | | async function save() { await formRef.value.validate(); if (!form.materialFiles) return proxy.$modal.msgError("请ä¸ä¼ ææéä»¶"); saving.value = true; try { await submitCustomerMaterial({ ...form }); proxy.$modal.msgSuccess("æææäº¤æå"); initialForm.value = formSnapshot(); visible.value = false; await load(); } finally { saving.value = false; } } |
| | | onMounted(load); |
| | | </script> |
| | | |
| | | <style scoped>.query-form { margin-top: 18px; }</style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :inline="true" :model="query"> |
| | | <el-form-item label="客æ·"><el-select v-model="query.customerId" filterable clearable style="width:240px"><el-option v-for="item in customers" :key="item.id" :label="item.customerName" :value="item.id" /></el-select></el-form-item> |
| | | <el-form-item label="年度"><el-input-number v-model="query.reportYear" :controls="false" /></el-form-item> |
| | | <el-form-item label="ç¶æ"><el-select v-model="query.status" clearable style="width:140px"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> |
| | | <el-form-item><el-button type="primary" @click="handleQuery">æ¥è¯¢</el-button><el-button @click="resetQuery">éç½®</el-button></el-form-item> |
| | | </el-form> |
| | | <el-table v-loading="loading" :data="rows" border> |
| | | <el-table-column prop="customerName" label="客æ·åç§°" min-width="200" /> |
| | | <el-table-column prop="reportYear" label="年度" width="90" align="center" /> |
| | | <el-table-column prop="reportName" label="ææåç§°" min-width="170" /> |
| | | <el-table-column label="ææéä»¶" min-width="210"><template #default="scope"><FileUpload v-model="scope.row.materialFiles" disabled /></template></el-table-column> |
| | | <el-table-column label="ç¶æ" width="100"><template #default="scope"><el-tag :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag></template></el-table-column> |
| | | <el-table-column prop="submitUserName" label="æäº¤äºº" width="110" /> |
| | | <el-table-column prop="createTime" label="æäº¤æ¶é´" width="170" /> |
| | | <el-table-column prop="reviewRemark" label="å®¡æ ¸æè§" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column prop="resultText" label="åå¸ç»æ" min-width="180" show-overflow-tooltip /> |
| | | <el-table-column label="ç»æéä»¶" min-width="180"><template #default="scope"><FileUpload v-if="scope.row.resultFiles" v-model="scope.row.resultFiles" disabled /><span v-else>-</span></template></el-table-column> |
| | | <el-table-column label="æä½" width="160" fixed="right"><template #default="scope"><el-button v-if="scope.row.status === 0" link type="primary" @click="openReview(scope.row, 1)">åç</el-button><el-button v-if="scope.row.status === 1" link type="success" @click="openReview(scope.row, 2)">åå¸</el-button><el-button v-if="[0, 1].includes(scope.row.status)" link type="danger" @click="openReview(scope.row, 3)">éå</el-button><span v-if="[2, 3].includes(scope.row.status)">-</span></template></el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" v-model:page="query.current" v-model:limit="query.size" @pagination="load" /> |
| | | <el-dialog v-model="visible" :title="dialogTitle" width="620px" append-to-body destroy-on-close :close-on-click-modal="false" @closed="resetReviewForm"> |
| | | <el-alert :title="`${currentRow.customerName || ''} · ${currentRow.reportYear || ''}年度 · ${currentRow.reportName || ''}`" type="info" :closable="false" class="review-summary" /> |
| | | <el-form ref="reviewFormRef" :model="form" label-width="100px"> |
| | | <el-form-item label="ç»æè¯´æ" v-if="form.status === 2"><el-input v-model="form.resultText" type="textarea" :rows="4" /></el-form-item> |
| | | <el-form-item label="ç»æéä»¶" v-if="form.status === 2"><FileUpload v-model="form.resultFiles" :limit="8" :file-size="20" /></el-form-item> |
| | | <el-form-item label="å®¡æ ¸æè§"><el-input v-model="form.reviewRemark" type="textarea" :rows="3" /></el-form-item> |
| | | </el-form> |
| | | <template #footer><el-button :disabled="saving" @click="visible=false">åæ¶</el-button><el-button type="primary" :loading="saving" @click="saveReview">ç¡®å®</el-button></template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FileUpload from "@/components/FileUpload/index.vue"; |
| | | import { listSubmissionManage, reviewSubmission } from "@/api/customerPortal/submission"; |
| | | import { listCustomer } from "@/api/basicData/customer"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const statusOptions = [{ label: "å¾
å®¡æ ¸", value: 0 }, { label: "å¤çä¸", value: 1 }, { label: "å·²åå¸", value: 2 }, { label: "å·²éå", value: 3 }]; |
| | | const query = reactive({ current: 1, size: 10, customerId: undefined, reportYear: undefined, status: undefined }); |
| | | const rows = ref([]); const customers = ref([]); const total = ref(0); const loading = ref(false); const visible = ref(false); const saving = ref(false); |
| | | const form = reactive({ id: null, status: 1, resultText: "", resultFiles: "", reviewRemark: "" }); |
| | | const currentRow = ref({}); const reviewFormRef = ref(); |
| | | const dialogTitle = computed(() => form.status === 2 ? "åå¸ç³æ¥ç»æ" : form.status === 3 ? "éåææ" : "åçææ"); |
| | | const statusText = (value) => statusOptions.find(item => item.value === value)?.label || "æªç¥"; |
| | | const statusType = (value) => ({ 0: "warning", 1: "primary", 2: "success", 3: "danger" }[value] || "info"); |
| | | async function load() { loading.value = true; try { const res = await listSubmissionManage(query); rows.value = res.data?.records || []; total.value = Number(res.data?.total || 0); } finally { loading.value = false; } } |
| | | function handleQuery() { query.current = 1; load(); } |
| | | function resetQuery() { Object.assign(query, { current: 1, size: query.size, customerId: undefined, reportYear: undefined, status: undefined }); load(); } |
| | | function openReview(row, status) { currentRow.value = row; Object.assign(form, { id: row.id, status, resultText: status === 2 ? row.resultText || "" : "", resultFiles: status === 2 ? row.resultFiles || "" : "", reviewRemark: "" }); visible.value = true; } |
| | | function resetReviewForm() { currentRow.value = {}; Object.assign(form, { id: null, status: 1, resultText: "", resultFiles: "", reviewRemark: "" }); reviewFormRef.value?.clearValidate(); } |
| | | async function saveReview() { if (form.status === 2 && !form.resultText.trim() && !form.resultFiles) return proxy.$modal.msgError("请填åç»æè¯´ææä¸ä¼ ç»æéä»¶"); if (form.status === 3 && !form.reviewRemark.trim()) return proxy.$modal.msgError("请è¾å
¥éååå "); saving.value = true; try { await reviewSubmission({ ...form }); proxy.$modal.msgSuccess(form.status === 1 ? "ææå·²åç" : form.status === 2 ? "ç»æå叿å" : "ææå·²éå"); visible.value = false; await load(); } finally { saving.value = false; } } |
| | | onMounted(async () => { const res = await listCustomer({ current: -1, size: -1 }); customers.value = res.data?.records || []; await load(); }); |
| | | </script> |
| | | |
| | | <style scoped>.review-summary { margin-bottom: 18px; }</style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | | <el-form-item label="ä¾åºååç§°ï¼"> |
| | | <el-input v-model="searchForm.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´ååå·ï¼"> |
| | | <el-input v-model="searchForm.purchaseContractNumber" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | </el-form-item> |
| | | <el-form-item label="éå®ååå·ï¼"> |
| | | <el-input v-model="searchForm.salesContractNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="项ç®åç§°ï¼"> |
| | | <el-input v-model="searchForm.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="å½å
¥æ¥æï¼"> |
| | | <el-date-picker v-model="searchForm.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="changeDaterange" /> |
| | | </el-form-item> |
| | | <el-form-item label="å
¥åºç¶æï¼"> |
| | | <el-select v-model="searchForm.stockInStatus" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 240px" |
| | | @change="handleQuery"> |
| | | <el-option label="å¾
å
¥åº" |
| | | value="å¾
å
¥åº" /> |
| | | <el-option label="å
¥åºä¸" |
| | | value="å
¥åºä¸" /> |
| | | <el-option label="å®å
¨å
¥åº" |
| | | value="å®å
¨å
¥åº" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery"> æç´¢ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <el-form :model="query" :inline="true"> |
| | | <el-form-item label="æ¯åºæ¥æï¼"> |
| | | <el-date-picker |
| | | v-model="dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | @change="handleDateChange" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼ï¼"> |
| | | <el-select |
| | | v-model="query.paymentMethod" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 180px" |
| | | @change="handleQuery" |
| | | > |
| | | <el-option v-for="item in paymentMethodOptions" :key="item" :label="item" :value="item" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <!-- <el-button type="success"--> |
| | | <!-- plain--> |
| | | <!-- @click="handleBatchGenerate">æ¹éçææ°æ®</el-button>--> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢å°è´¦</el-button> |
| | | <el-button type="primary" |
| | | plain |
| | | @click="handleImport">导å
¥</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤ |
| | | </el-button> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢</el-button> |
| | | <el-button @click="handleExport">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 21.5em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-table :data="props.row.children" |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="产å大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="specificationModel" /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" /> |
| | | <el-table-column label="å
¥åºå®¡æ ¸ç¶æ" |
| | | prop="stockInApprovalStatus" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" |
| | | size="small"> |
| | | {{ scope.row.stockInApprovalStatus || '--' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ°é" |
| | | prop="quantity" /> |
| | | <el-table-column label="éå®åè´§æ°é" |
| | | prop="shippedQuantity" /> |
| | | <el-table-column label="å¯ç¨æ°é" |
| | | prop="availableQuality" /> |
| | | <el-table-column label="éè´§æ°é" |
| | | prop="returnQuantity" /> |
| | | <el-table-column label="ç¨ç(%)" |
| | | prop="taxRate" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | </el-table> |
| | | </template> |
| | | |
| | | <el-table |
| | | v-loading="loading" |
| | | :data="tableData" |
| | | border |
| | | height="calc(100vh - 21.5em)" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="åºå·" type="index" width="60" align="center" /> |
| | | <el-table-column label="éè´æ¥æ" prop="purchaseDate" width="120" align="center" /> |
| | | <el-table-column label="éé¢" prop="amount" min-width="120" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.amount) }}</template> |
| | | </el-table-column> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="éè´ååå·" |
| | | prop="purchaseContractNumber" |
| | | width="160" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | width="160" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | width="160" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="项ç®åç§°" |
| | | prop="projectName" |
| | | width="320" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | width="100" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" |
| | | size="small"> |
| | | {{ approvalStatusText[scope.row.approvalStatus] || 'æªç¥ç¶æ' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºç¶æ" |
| | | prop="stockInStatus" |
| | | width="100" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStockInStatusType(scope.row.stockInStatus)" |
| | | size="small"> |
| | | {{ scope.row.stockInStatus || '--' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¾è®¢æ¥æ" |
| | | prop="executionDate" |
| | | width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="仿¬¾æ¹å¼" |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="å®é
ååéé¢(å
)" |
| | | prop="netContractAmount" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | :formatter="(_, __, cellValue) => formatMoney(cellValue)" /> |
| | | <el-table-column label="å½å
¥äºº" |
| | | prop="recorderName" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" |
| | | prop="remarks" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openForm('edit', scope.row)" |
| | | :disabled="scope.row.approvalStatus === 3">ç¼è¾ |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openFileDialog(scope.row)">éä»¶</el-button> |
| | | <el-table-column label="éè´ç©å" prop="purchaseGoods" min-width="180" show-overflow-tooltip /> |
| | | <el-table-column label="å票å·ç " prop="invoiceNumber" min-width="150" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥äºº" prop="recorderName" width="120" align="center" /> |
| | | <el-table-column label="å½å
¥æ¥æ" prop="entryDate" width="120" align="center" /> |
| | | <el-table-column label="æä½" width="140" fixed="right" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="primary" @click="openForm('edit', row)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="openFileDialog(row)">éä»¶</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="query.current" |
| | | :limit="query.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | <FormDialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢éè´å°è´¦é¡µé¢' : 'ç¼è¾éè´å°è´¦é¡µé¢'" |
| | | :width="'70%'" |
| | | :operation-type="operationType" |
| | | @close="closeDia" |
| | | @confirm="submitForm" |
| | | @cancel="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éè´ååå·ï¼" |
| | | prop="purchaseContractNumber"> |
| | | <el-input v-model="form.purchaseContractNumber" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éå®ååå·ï¼" |
| | | prop="salesLedgerId"> |
| | | <el-select v-model="form.salesLedgerId" |
| | | placeholder="è¯·éæ©" |
| | | filterable |
| | | clearable |
| | | @change="salesLedgerChange"> |
| | | <el-option v-for="item in salesContractList" |
| | | :key="item.id" |
| | | :label="item.salesContractNo" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºååç§°ï¼" |
| | | prop="supplierId"> |
| | | <el-select v-model="form.supplierId" |
| | | placeholder="è¯·éæ©" |
| | | filterable |
| | | clearable> |
| | | <el-option v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id">{{ item.supplierName + '---' + item.supplierType }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°" |
| | | prop="projectName"> |
| | | <el-input v-model="form.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼"> |
| | | <el-input v-model="form.paymentMethod" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¾è®¢æ¥æï¼" |
| | | prop="executionDate"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.executionDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼" |
| | | prop="recorderId"> |
| | | <el-select v-model="form.recorderId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥æ¥æï¼" |
| | | prop="entryDate"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产åä¿¡æ¯ï¼" |
| | | prop="entryDate"> |
| | | <el-button type="primary" |
| | | @click="openProductForm('add')">æ·»å |
| | | </el-button> |
| | | <el-button plain |
| | | type="danger" |
| | | @click="deleteProduct">å é¤ |
| | | </el-button> |
| | | </el-form-item> |
| | | <div class="select-button-group" |
| | | style="width: 500px; margin: 20px 0;" |
| | | v-if="operationType === 'add'"> |
| | | <el-select filterable |
| | | allow-create |
| | | :reserve-keyword="true" |
| | | :default-first-option="false" |
| | | clearable |
| | | v-model="templateName" |
| | | :input-value="filterInputValue" |
| | | @filter-change="onTemplateFilterChange" |
| | | @change="onTemplateChange" |
| | | @focus="getTemplateList" |
| | | style="width: 500px;" |
| | | placeholder="è¯·éæ©æ¨¡çæè
è¾å
¥æ°ç模çåç§°åéæ©" |
| | | class="no-arrow-select"> |
| | | <el-option v-for="item in templateList" |
| | | :key="item.id || item.value" |
| | | :label="item.templateName" |
| | | :value="item.templateName"> |
| | | <div style="display: flex; justify-content: space-between; align-items: center;"> |
| | | <span>{{ item.templateName }}</span> |
| | | <el-icon v-if="item.id" |
| | | class="delete-icon" |
| | | @click.stop="handleDeleteTemplate(item)" |
| | | style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;"> |
| | | <Delete /> |
| | | </el-icon> |
| | | </div> |
| | | </el-option> |
| | | </el-select> |
| | | <!-- æé®ï¼ä¸ Select é«åº¦å¹é
ï¼å»æå·¦ä¾§è¾¹æ¡ï¼æ ç¼è¡æ¥ --> |
| | | <el-button size="small" |
| | | style="height: 32px;margin-left: 8px;" |
| | | @click="handleButtonClick" |
| | | :disabled="!templateName || templateName.trim() === '' || (!currentTemplateId && isTemplateNameDuplicate)"> |
| | | ä¿å |
| | | </el-button> |
| | | </div> |
| | | </el-row> |
| | | <el-table :data="productData" |
| | | border |
| | | @selection-change="productSelected" |
| | | show-summary |
| | | :summary-method="summarizeProTable"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="产å大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="specificationModel" /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | width="70" /> |
| | | <el-table-column label="æ°é" |
| | | prop="quantity" |
| | | width="70" /> |
| | | <el-table-column label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ç¨ç(%)" |
| | | prop="taxRate" |
| | | width="80" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="æ¯å¦è´¨æ£" |
| | | prop="isChecked" |
| | | width="150"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> |
| | | {{ scope.row.isChecked ? 'æ¯' : 'å¦' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¥åºå®¡æ ¸ç¶æ" |
| | | prop="stockInApprovalStatus" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" |
| | | size="small"> |
| | | {{ scope.row.stockInApprovalStatus || '--' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openProductForm('edit', scope.row, scope.$index)" |
| | | :disabled="scope.row.stockInApprovalStatus === 'å®å
¨å
¥åº'">ç¼è¾ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨Â·ï¼" |
| | | prop="remarks"> |
| | | <el-input v-model="form.remarks" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | type="textarea" |
| | | :rows="2" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="éä»¶ææï¼" |
| | | prop="purchaseLedgerFiles"> |
| | | <FileUpload v-model:file-list="fileList" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-dialog |
| | | v-model="formVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢éè´å°è´¦' : 'ç¼è¾éè´å°è´¦'" |
| | | width="640px" |
| | | destroy-on-close |
| | | append-to-body |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-form-item label="éè´æ¥æï¼" prop="purchaseDate"> |
| | | <el-date-picker |
| | | v-model="form.purchaseDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¯åºæ¥æï¼" prop="paymentDate"> |
| | | <el-date-picker |
| | | v-model="form.paymentDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼ï¼" prop="paymentMethod"> |
| | | <el-select |
| | | v-model="form.paymentMethod" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | allow-create |
| | | default-first-option |
| | | style="width: 100%" |
| | | > |
| | | <el-option v-for="item in paymentMethodOptions" :key="item" :label="item" :value="item" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="éé¢(å
)ï¼" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :precision="2" |
| | | controls-position="right" |
| | | placeholder="请è¾å
¥" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="éè´ç©åï¼" prop="purchaseGoods"> |
| | | <el-input v-model="form.purchaseGoods" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="å票å·ç ï¼" prop="invoiceNumber"> |
| | | <el-input v-model="form.invoiceNumber" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥" maxlength="500" show-word-limit /> |
| | | </el-form-item> |
| | | <el-form-item label="éä»¶ï¼" prop="attachments"> |
| | | <AttachmentUploadFile v-model:file-list="formFiles" :limit="10" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </FormDialog> |
| | | <!-- 导å
¥å¼¹çª --> |
| | | <FormDialog v-model="importUpload.open" |
| | | :title="importUpload.title" |
| | | :width="'600px'" |
| | | @close="importUpload.open = false" |
| | | @confirm="submitImportFile" |
| | | @cancel="importUpload.open = false"> |
| | | <el-upload ref="importUploadRef" |
| | | :limit="1" |
| | | accept=".xlsx,.xls" |
| | | :action="importUpload.url" |
| | | :headers="importUpload.headers" |
| | | :before-upload="importUpload.beforeUpload" |
| | | :on-success="importUpload.onSuccess" |
| | | :on-error="importUpload.onError" |
| | | :on-progress="importUpload.onProgress" |
| | | :on-change="importUpload.onChange" |
| | | :auto-upload="false" |
| | | drag> |
| | | <i class="el-icon-upload"></i> |
| | | <div class="el-upload__text"> |
| | | å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em> |
| | | </div> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | ä»
æ¯æ xls/xlsxï¼å¤§å°ä¸è¶
è¿ 10MBã |
| | | <el-button link |
| | | type="primary" |
| | | @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </FormDialog> |
| | | <FormDialog v-model="productFormVisible" |
| | | :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" |
| | | :width="'40%'" |
| | | :operation-type="productOperationType" |
| | | @close="closeProductDia" |
| | | @confirm="submitProduct" |
| | | @cancel="closeProductDia"> |
| | | <el-form :model="productForm" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="productRules" |
| | | ref="productFormRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产å大类ï¼" |
| | | prop="productId"> |
| | | <el-tree-select v-model="productForm.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" |
| | | prop="productModelId"> |
| | | <el-select v-model="productForm.productModelId" |
| | | placeholder="è¯·éæ©" |
| | | filterable |
| | | clearable |
| | | @change="getProductModel"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åä½ï¼" |
| | | prop="unit"> |
| | | <el-input v-model="productForm.unit" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç(%)ï¼" |
| | | prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="mathNum"> |
| | | <el-option v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | | :label="dict.label" |
| | | :value="dict.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨åä»·(å
)ï¼" |
| | | prop="taxInclusiveUnitPrice"> |
| | | <el-input-number v-model="productForm.taxInclusiveUnitPrice" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="mathNum" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ°éï¼" |
| | | prop="quantity"> |
| | | <el-input-number :step="0.1" |
| | | clearable |
| | | :precision="3" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="productForm.quantity" |
| | | placeholder="请è¾å
¥" |
| | | @change="mathNum" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨æ»ä»·(å
)ï¼" |
| | | prop="taxInclusiveTotalPrice"> |
| | | <el-input-number v-model="productForm.taxInclusiveTotalPrice" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="reverseMathNum('taxInclusiveTotalPrice')" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å«ç¨æ»ä»·(å
)ï¼" |
| | | prop="taxExclusiveTotalPrice"> |
| | | <el-input-number v-model="productForm.taxExclusiveTotalPrice" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="reverseMathNum('taxExclusiveTotalPrice')" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类åï¼" |
| | | prop="invoiceType"> |
| | | <el-select v-model="productForm.invoiceType" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="墿®ç¥¨" |
| | | value="墿®ç¥¨" /> |
| | | <el-option label="å¢ä¸ç¥¨" |
| | | value="å¢ä¸ç¥¨" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåé¢è¦æ°éï¼" |
| | | prop="warnNum"> |
| | | <el-input-number v-model="productForm.warnNum" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦è´¨æ£ï¼" |
| | | prop="isChecked"> |
| | | <el-radio-group v-model="productForm.isChecked"> |
| | | <el-radio label="æ¯" |
| | | :value="true" /> |
| | | <el-radio label="å¦" |
| | | :value="false" /> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <FileList v-if="fileListDialogVisible" |
| | | v-model:visible="fileListDialogVisible" |
| | | record-type="purchase_ledger" |
| | | :record-id="recordId" /> |
| | | <template #footer> |
| | | <el-button @click="formVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="saving" @click="submit">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <FileList |
| | | v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | record-type="purchase_ledger_simple" |
| | | :record-id="currentRowId" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import FileListDialog from "@/components/Dialog/FileListDialog.vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | reactive, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | } from "vue"; |
| | | import { Search, Delete } from "@element-plus/icons-vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, |
| | | getProductInfoByContractNo, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { |
| | | addOrEditPurchase, |
| | | addPurchaseTemplate, |
| | | updatePurchaseTemplate, |
| | | createPurchaseNo, |
| | | delPurchase, |
| | | getSalesNo, |
| | | purchaseListPage, |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | getPurchaseTemplateList, |
| | | delPurchaseTemplate, |
| | | batchGeneratePurchaseInboundSteps, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | <script setup name="ProcurementLedger"> |
| | | import { getCurrentInstance, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import pagination from '@/components/PIMTable/Pagination.vue' |
| | | import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue' |
| | | import FileList from '@/components/Dialog/FileList.vue' |
| | | import { simpleLedgerList, simpleLedgerGet, simpleLedgerSave, simpleLedgerDelete } from '@/api/procurementManagement/procurementLedger.js' |
| | | import { attachmentList, createAttachment } from '@/api/basicData/storageAttachment.js' |
| | | import useUserStore from '@/store/modules/user' |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const productSelectedRows = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); |
| | | const productOptions = ref([]); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const recordId = ref(); |
| | | const fileListDialogVisible = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import dayjs from "dayjs"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | const { proxy } = getCurrentInstance() |
| | | const userStore = useUserStore() |
| | | |
| | | const userStore = useUserStore(); |
| | | const RECORD_TYPE = 'purchase_ledger_simple' |
| | | const paymentMethodOptions = ['ç°é', 'é¶è¡è½¬è´¦', 'æ¿å
æ±ç¥¨', 'å
¶ä»'] |
| | | |
| | | // 订å审æ¹ç¶ææ¾ç¤ºææ¬ |
| | | const approvalStatusText = { |
| | | 1: "å¾
å®¡æ ¸", |
| | | 2: "审æ¹ä¸", |
| | | 3: "审æ¹éè¿", |
| | | 4: "审æ¹å¤±è´¥", |
| | | }; |
| | | const loading = ref(false) |
| | | const saving = ref(false) |
| | | const total = ref(0) |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const dateRange = ref([]) |
| | | |
| | | // è·å审æ¹ç¶ææ ç¾ç±»å |
| | | const getApprovalStatusType = status => { |
| | | const typeMap = { |
| | | 1: "info", // å¾
å®¡æ ¸ - ç°è² |
| | | 2: "warning", // 审æ¹ä¸ - æ©è² |
| | | 3: "success", // 审æ¹éè¿ - ç»¿è² |
| | | 4: "danger", // 审æ¹å¤±è´¥ - çº¢è² |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | | const query = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | paymentDateStart: undefined, |
| | | paymentDateEnd: undefined, |
| | | paymentMethod: undefined |
| | | }) |
| | | |
| | | // è·åå
¥åºç¶ææ ç¾ç±»å |
| | | const getStockInStatusType = status => { |
| | | const typeMap = { |
| | | å¾
å
¥åº: "info", // å¾
å
¥åº - ç°è² |
| | | å
¥åºä¸: "warning", // å
¥åºä¸ - æ©è² |
| | | å®å
¨å
¥åº: "success", // å®å
¨å
¥åº - ç»¿è² |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | | const formVisible = ref(false) |
| | | const operationType = ref('add') |
| | | const formRef = ref() |
| | | const formFiles = ref([]) |
| | | const fileDialogVisible = ref(false) |
| | | const currentRowId = ref(0) |
| | | |
| | | // è·åå
¥åºå®¡æ ¸ç¶ææ ç¾ç±»å |
| | | const getStockInApprovalStatusType = status => { |
| | | const typeMap = { |
| | | å¾
å
¥åº: "info", // å¾
å
¥åº - ç°è² |
| | | å
¥åºä¸: "warning", // å
¥åºä¸ - æ©è² |
| | | å®å
¨å
¥åº: "success", // å®å
¨å
¥åº - ç»¿è² |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | | const form = ref({ |
| | | id: undefined, |
| | | purchaseDate: '', |
| | | paymentDate: '', |
| | | paymentMethod: '', |
| | | amount: undefined, |
| | | purchaseGoods: '', |
| | | invoiceNumber: '', |
| | | remark: '' |
| | | }) |
| | | |
| | | const templateName = ref(""); |
| | | const filterInputValue = ref(""); |
| | | const templateList = ref([]); |
| | | const isTemplateNameDuplicate = ref(false); // æ 记模æ¿åç§°æ¯å¦éå¤ |
| | | // å½åéä¸ç模æ¿IDï¼ç¨äºåºåæ°å¢æ¨¡æ¿è¿æ¯æ´æ°æ¨¡æ¿ï¼ |
| | | const currentTemplateId = ref(null); |
| | | const rules = { |
| | | purchaseDate: [{ required: true, message: 'è¯·éæ©éè´æ¥æ', trigger: 'change' }], |
| | | amount: [{ required: true, message: '请è¾å
¥éé¢', trigger: 'blur' }], |
| | | purchaseGoods: [{ required: true, message: '请è¾å
¥éè´ç©å', trigger: 'blur' }] |
| | | } |
| | | |
| | | // æ£æ¥æ¨¡æ¿åç§°æ¯å¦éå¤ |
| | | const checkTemplateNameDuplicate = name => { |
| | | if (!name || name.trim() === "") { |
| | | isTemplateNameDuplicate.value = false; |
| | | return false; |
| | | } |
| | | const isDuplicate = templateList.value.some( |
| | | item => item.templateName === name.trim() |
| | | ); |
| | | isTemplateNameDuplicate.value = isDuplicate; |
| | | return isDuplicate; |
| | | }; |
| | | function todayStr() { |
| | | const d = new Date() |
| | | const pad = n => String(n).padStart(2, '0') |
| | | return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` |
| | | } |
| | | |
| | | // 鲿宿¶å¨ |
| | | let duplicateCheckTimer = null; |
| | | const onTemplateFilterChange = val => { |
| | | filterInputValue.value = val ?? ""; |
| | | // æ¸
é¤ä¹åç宿¶å¨ |
| | | if (duplicateCheckTimer) { |
| | | clearTimeout(duplicateCheckTimer); |
| | | } |
| | | // 宿¶æ£æ¥æ¨¡æ¿åç§°æ¯å¦éå¤ï¼é²æå¤çï¼é¿å
é¢ç¹æç¤ºï¼ |
| | | if (val && val.trim()) { |
| | | duplicateCheckTimer = setTimeout(() => { |
| | | const isDuplicate = checkTemplateNameDuplicate(val); |
| | | if (isDuplicate) { |
| | | ElMessage({ |
| | | message: "模æ¿åç§°å·²åå¨ï¼è¯·æ´æ¢æ¨¡æ¿åç§°", |
| | | type: "warning", |
| | | duration: 2000, |
| | | }); |
| | | } |
| | | }, 300); // 300ms 鲿 |
| | | } else { |
| | | isTemplateNameDuplicate.value = false; |
| | | } |
| | | }; |
| | | function formatAmount(val) { |
| | | const num = Number(val) |
| | | return Number.isFinite(num) ? num.toFixed(2) : '-' |
| | | } |
| | | |
| | | // allow-create æ¶ï¼è¾å
¥ä¸åå¨çå
容ä¼ä½ä¸º string å¼è¿åï¼è¿é忥åè¾å
¥æ¡ä»¥ç¡®ä¿æåä¸ä¸¢ |
| | | const onTemplateChange = async val => { |
| | | if (typeof val === "string") { |
| | | filterInputValue.value = val; |
| | | // éæ©æè¾å
¥æ¶æ£æ¥éå¤ |
| | | checkTemplateNameDuplicate(val); |
| | | } |
| | | function getList() { |
| | | loading.value = true |
| | | simpleLedgerList({ ...query }) |
| | | .then(res => { |
| | | const data = res?.data || {} |
| | | tableData.value = data.records || [] |
| | | total.value = Number(data.total) || 0 |
| | | }) |
| | | .catch(() => { |
| | | tableData.value = [] |
| | | total.value = 0 |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | |
| | | // è¿æ»¤æ°æ®ï¼æ¥æ¾å¹é
çæ¨¡æ¿ |
| | | const matchedTemplate = templateList.value.find( |
| | | item => item.templateName === val |
| | | ); |
| | | function handleQuery() { |
| | | query.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | if (matchedTemplate?.id) { |
| | | // è®°å½å½åéä¸ç模æ¿IDï¼åç»ä¿åæ¶è¿è¡æ´æ°æä½ |
| | | currentTemplateId.value = matchedTemplate.id; |
| | | // éä¸å·²ææ¨¡æ¿æ¶ï¼ä¸åºè§ä¸ºâ模æ¿åç§°éå¤å¯¼è´ä¸å¯ä¿åâ |
| | | isTemplateNameDuplicate.value = false; |
| | | // 妿æ¾å°æ¨¡æ¿ï¼åªèµå¼ä¾åºåã项ç®åç§°ã仿¬¾æ¹å¼å产åä¿¡æ¯ |
| | | if (matchedTemplate.supplierId) { |
| | | form.value.supplierId = matchedTemplate.supplierId; |
| | | } |
| | | if (matchedTemplate.supplierName) { |
| | | form.value.supplierName = matchedTemplate.supplierName; |
| | | } |
| | | if (matchedTemplate.projectName) { |
| | | form.value.projectName = matchedTemplate.projectName; |
| | | } |
| | | if (matchedTemplate.paymentMethod) { |
| | | form.value.paymentMethod = matchedTemplate.paymentMethod; |
| | | } |
| | | // æ¨¡æ¿æ°æ®ä¸ç产ååæ®µæ¯ productListï¼éè¦è½¬æ¢ä¸º productData |
| | | productData.value = |
| | | matchedTemplate.productList || matchedTemplate.productData || []; |
| | | } else { |
| | | // æªå¹é
å°å·²ææ¨¡æ¿ï¼è§ä¸ºæ°æ¨¡æ¿ |
| | | currentTemplateId.value = null; |
| | | // å¦ææ²¡ææ¾å°æ¨¡æ¿ï¼é置表åï¼ä¿æå½å表åç¶æï¼ |
| | | const currentFormData = { ...form.value }; |
| | | const currentProductData = [...productData.value]; |
| | | function handleDateChange(val) { |
| | | query.paymentDateStart = val?.[0] |
| | | query.paymentDateEnd = val?.[1] |
| | | handleQuery() |
| | | } |
| | | |
| | | // å¦æå¯¹è¯æ¡æªæå¼ï¼å
æå¼ |
| | | if (!dialogFormVisible.value) { |
| | | operationType.value = "add"; |
| | | dialogFormVisible.value = true; |
| | | } |
| | | function resetQuery() { |
| | | dateRange.value = [] |
| | | query.paymentDateStart = undefined |
| | | query.paymentDateEnd = undefined |
| | | query.paymentMethod = undefined |
| | | query.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // çå¾
ä¸ä¸ä¸ª tick 忢夿°æ® |
| | | await nextTick(); |
| | | form.value = { |
| | | ...form.value, |
| | | ...currentFormData, |
| | | }; |
| | | productData.value = currentProductData; |
| | | } |
| | | }; |
| | | function paginationChange(obj) { |
| | | query.current = obj.page |
| | | query.size = obj.limit |
| | | getList() |
| | | } |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", // ä¾åºååç§° |
| | | purchaseContractNumber: "", // éè´ååç¼å· |
| | | salesContractNo: "", // éå®ååç¼å· |
| | | projectName: "", // 项ç®åç§° |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | function handleSelectionChange(rows) { |
| | | selectedRows.value = rows |
| | | } |
| | | |
| | | function handleExport() { |
| | | proxy.download( |
| | | 'purchase/simpleLedger/export', |
| | | { |
| | | paymentDateStart: query.paymentDateStart, |
| | | paymentDateEnd: query.paymentDateEnd, |
| | | paymentMethod: query.paymentMethod |
| | | }, |
| | | form: { |
| | | purchaseContractNumber: "", |
| | | salesLedgerId: "", |
| | | projectName: "", |
| | | recorderId: "", |
| | | entryDate: "", |
| | | productData: [], |
| | | supplierName: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | executionDate: "", |
| | | isChecked: false, |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: false, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | projectName: [ |
| | | { required: true, message: "请è¾å
¥é¡¹ç®åç§°", trigger: "blur" }, |
| | | ], |
| | | supplierId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData({ |
| | | ...data.searchForm, |
| | | // 设置å½å
¥æ¥æèå´ä¸ºå½å¤© |
| | | entryDate: [ |
| | | dayjs().startOf("day").format("YYYY-MM-DD"), |
| | | dayjs().endOf("day").format("YYYY-MM-DD"), |
| | | ], |
| | | entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"), |
| | | entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"), |
| | | }); |
| | | 'éè´å°è´¦.xlsx' |
| | | ) |
| | | } |
| | | |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | const productOperationType = ref(""); |
| | | const productOperationIndex = ref(""); |
| | | const currentId = ref(""); |
| | | const productFormData = reactive({ |
| | | productForm: { |
| | | productId: "", |
| | | productCategory: "", |
| | | productModelId: "", |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | | taxInclusiveUnitPrice: "", |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | warnNum: "", |
| | | isChecked: false, |
| | | }, |
| | | productRules: { |
| | | productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | warnNum: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | isChecked: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | const upload = reactive({ |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }); |
| | | |
| | | // 导å
¥ç¸å
³ |
| | | const importUploadRef = ref(null); |
| | | const importUpload = reactive({ |
| | | title: "导å
¥éè´å°è´¦", |
| | | open: false, |
| | | url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import", |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | isUploading: false, |
| | | beforeUpload: file => { |
| | | const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); |
| | | const isLt10M = file.size / 1024 / 1024 < 10; |
| | | if (!isExcel) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶åªè½æ¯ xlsx/xls æ ¼å¼!"); |
| | | return false; |
| | | } |
| | | if (!isLt10M) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿ 10MB!"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | onChange: (file, fileList) => { |
| | | // noop |
| | | }, |
| | | onProgress: (event, file, fileList) => { |
| | | // noop |
| | | }, |
| | | onSuccess: (response, file, fileList) => { |
| | | importUpload.isUploading = false; |
| | | if (response?.code === 200) { |
| | | proxy.$modal.msgSuccess("导å
¥æå"); |
| | | importUpload.open = false; |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles?.(); |
| | | } |
| | | getList(); |
| | | function handleDelete() { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning('è¯·éæ©è¦å é¤çæ°æ®') |
| | | return |
| | | } |
| | | const ids = selectedRows.value.map(r => r.id) |
| | | ElMessageBox.confirm(`确认å é¤éä¸ç ${ids.length} æ¡æ°æ®åï¼`, 'å é¤ç¡®è®¤', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }) |
| | | .then(() => simpleLedgerDelete(ids)) |
| | | .then(res => { |
| | | if (res?.code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | } else { |
| | | proxy.$modal.msgError(response?.msg || "导å
¥å¤±è´¥"); |
| | | ElMessage.error(res?.msg || 'å é¤å¤±è´¥') |
| | | } |
| | | }, |
| | | onError: () => { |
| | | importUpload.isUploading = false; |
| | | proxy.$modal.msgError("导å
¥å¤±è´¥ï¼è¯·éè¯"); |
| | | }, |
| | | }); |
| | | }) |
| | | .catch(() => {}) |
| | | } |
| | | |
| | | const handleImport = () => { |
| | | importUpload.title = "导å
¥éè´å°è´¦"; |
| | | importUpload.open = true; |
| | | importUpload.isUploading = false; |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles?.(); |
| | | async function loadAttachments(recordId) { |
| | | try { |
| | | const res = await attachmentList({ recordType: RECORD_TYPE, recordId }) |
| | | const list = res?.data || [] |
| | | return (Array.isArray(list) ? list : []).map(item => ({ |
| | | id: item.id, |
| | | name: item.name || item.originalFilename || 'éä»¶', |
| | | url: item.url || item.downloadURL || item.previewURL || '' |
| | | })) |
| | | } catch { |
| | | return [] |
| | | } |
| | | } |
| | | |
| | | async function openForm(type, row) { |
| | | operationType.value = type |
| | | formRef.value?.resetFields?.() |
| | | formFiles.value = [] |
| | | form.value = { |
| | | id: undefined, |
| | | purchaseDate: '', |
| | | paymentDate: '', |
| | | paymentMethod: '', |
| | | amount: undefined, |
| | | purchaseGoods: '', |
| | | invoiceNumber: '', |
| | | remark: '' |
| | | } |
| | | if (type === 'edit' && row?.id) { |
| | | const detail = (await simpleLedgerGet(row.id))?.data || row |
| | | form.value = { |
| | | id: detail.id, |
| | | purchaseDate: detail.purchaseDate || '', |
| | | paymentDate: detail.paymentDate || '', |
| | | paymentMethod: detail.paymentMethod || '', |
| | | amount: detail.amount === null || detail.amount === undefined ? undefined : Number(detail.amount), |
| | | purchaseGoods: detail.purchaseGoods || '', |
| | | invoiceNumber: detail.invoiceNumber || '', |
| | | remark: detail.remark || '' |
| | | } |
| | | }; |
| | | formFiles.value = await loadAttachments(detail.id) |
| | | } else { |
| | | form.value.purchaseDate = todayStr() |
| | | } |
| | | formVisible.value = true |
| | | } |
| | | |
| | | // ä¸è½½å¯¼å
¥æ¨¡æ¿ï¼å¦å端路å¾ä¸åï¼å¯å¨æ¤å¤è°æ´ï¼ |
| | | const downloadTemplate = () => { |
| | | proxy.download( |
| | | "/purchase/ledger/exportTemplate", |
| | | {}, |
| | | "éè´å°è´¦å¯¼å
¥æ¨¡æ¿.xlsx" |
| | | ); |
| | | }; |
| | | |
| | | const submitImportFile = () => { |
| | | importUpload.isUploading = true; |
| | | proxy.$refs["importUploadRef"]?.submit?.(); |
| | | }; |
| | | |
| | | const changeDaterange = value => { |
| | | if (value) { |
| | | searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } else { |
| | | searchForm.entryDateStart = undefined; |
| | | searchForm.entryDateEnd = undefined; |
| | | async function submit() { |
| | | try { |
| | | await formRef.value?.validate?.() |
| | | } catch { |
| | | return |
| | | } |
| | | saving.value = true |
| | | try { |
| | | const payload = { ...form.value } |
| | | if (!payload.id) { |
| | | payload.recorderId = userStore.id |
| | | payload.recorderName = userStore.nickName || userStore.name |
| | | payload.entryDate = todayStr() |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // ä¿åæ¨¡æ¿ |
| | | const handleButtonClick = async () => { |
| | | // æ£æ¥æ¨¡æ¿åç§°æ¯å¦ä¸ºç©º |
| | | if (!templateName.value || templateName.value.trim() === "") { |
| | | ElMessage({ |
| | | message: "请è¾å
¥æ¨¡æ¿åç§°", |
| | | type: "warning", |
| | | }); |
| | | return; |
| | | const res = await simpleLedgerSave(payload) |
| | | if (res?.code !== 200) { |
| | | ElMessage.error(res?.msg || 'ä¿å失败') |
| | | return |
| | | } |
| | | |
| | | // 妿æ¯âæ°å¢æ¨¡æ¿âï¼æ²¡æéä¸å·²ææ¨¡æ¿ï¼ï¼æéè¦åéåæ ¡éªï¼ |
| | | // è¥æ¯éä¸å·²ææ¨¡æ¿åä¿®æ¹ï¼åå
许使ç¨ååç§°ï¼è§ä¸ºæ´æ°ï¼ |
| | | if (!currentTemplateId.value) { |
| | | const isDuplicate = checkTemplateNameDuplicate(templateName.value); |
| | | if (isDuplicate) { |
| | | ElMessage({ |
| | | message: "模æ¿åç§°å·²åå¨ï¼è¯·æ´æ¢æ¨¡æ¿åç§°", |
| | | type: "warning", |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥ä¾åºåæ¯å¦éæ© |
| | | if (!form.value.supplierId) { |
| | | ElMessage({ |
| | | message: "请å
éæ©ä¾åºå", |
| | | type: "warning", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦æäº§åæ°æ® |
| | | if (!productData.value || productData.value.length === 0) { |
| | | ElMessage({ |
| | | message: "请å
æ·»å 产åä¿¡æ¯", |
| | | type: "warning", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | let params = { |
| | | productData: proxy.HaveJson(productData.value), |
| | | supplierId: form.value.supplierId, |
| | | paymentMethod: form.value.paymentMethod, |
| | | recorderId: form.value.recorderId, |
| | | projectName: form.value.projectName, |
| | | templateName: templateName.value.trim(), |
| | | }; |
| | | console.log( |
| | | "template params ===>", |
| | | params, |
| | | "currentTemplateId:", |
| | | currentTemplateId.value |
| | | ); |
| | | |
| | | // 妿 currentTemplateId æå¼ï¼è¯´æå½åæ¯âç¼è¾å·²ææ¨¡æ¿â â è°ç¨æ´æ°æ¥å£ |
| | | // å¦åä¸ºâæ°å»ºæ¨¡æ¿â â è°ç¨æ°å¢æ¥å£ |
| | | let res; |
| | | if (currentTemplateId.value) { |
| | | res = await updatePurchaseTemplate({ |
| | | id: currentTemplateId.value, |
| | | ...params, |
| | | }); |
| | | } else { |
| | | res = await addPurchaseTemplate(params); |
| | | } |
| | | |
| | | if (res && res.code === 200) { |
| | | ElMessage({ |
| | | message: currentTemplateId.value ? "æ¨¡æ¿æ´æ°æå" : "模æ¿ä¿åæå", |
| | | type: "success", |
| | | }); |
| | | // ä¿åæååéæ°è·å模æ¿å表 |
| | | await getTemplateList(); |
| | | // æ¸
空模æ¿åç§°è¾å
¥ |
| | | templateName.value = ""; |
| | | filterInputValue.value = ""; |
| | | isTemplateNameDuplicate.value = false; |
| | | // ä¿å/æ´æ°å®æåï¼éç½®å½å模æ¿ID |
| | | currentTemplateId.value = null; |
| | | } else { |
| | | ElMessage({ |
| | | message: res?.msg || "模æ¿ä¿å失败", |
| | | type: "error", |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("ä¿å模æ¿å¤±è´¥:", error); |
| | | ElMessage({ |
| | | message: "模æ¿ä¿å失败ï¼è¯·ç¨åéè¯", |
| | | type: "error", |
| | | }); |
| | | } |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "ticketsNum", |
| | | "ticketsAmount", |
| | | "futureTickets", |
| | | "futureTicketsAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { entryDate, ...rest } = searchForm; |
| | | purchaseListPage({ ...rest, ...page }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | // tableData.value = res.data.records; |
| | | tableData.value = res.data.records.map(record => ({ |
| | | ...record, |
| | | })); |
| | | // åå§ååæ°æ®æ°ç» |
| | | tableData.value.forEach(item => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | expandedRowKeys.value = []; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const productSelected = selectedRows => { |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | // å±å¼è¡ |
| | | const expandChange = async (row, expandedRows) => { |
| | | if (expandedRows.length > 0) { |
| | | expandedRowKeys.value = []; |
| | | const recordId = res.data || form.value.id |
| | | const blobs = formFiles.value |
| | | .map(f => ({ |
| | | id: f.id ?? f.rawData?.id, |
| | | application: 'file', |
| | | name: f.name, |
| | | url: f.url, |
| | | downloadURL: f.url |
| | | })) |
| | | .filter(f => f.id) |
| | | if (blobs.length > 0) { |
| | | try { |
| | | const res = await productList({ salesLedgerId: row.id, type: 2 }); |
| | | const index = tableData.value.findIndex(item => item.id === row.id); |
| | | if (index > -1) { |
| | | tableData.value[index].children = res.data || []; |
| | | expandedRowKeys.value.push(row.id); |
| | | } |
| | | } catch (error) { |
| | | console.error("å 载产åå表失败:", error); |
| | | proxy.$modal.msgError("å 载产åå表失败"); |
| | | // å±å¼å¤±è´¥æ¶ï¼ç§»é¤å±å¼ç¶æ |
| | | const index = expandedRows.findIndex(item => item.id === row.id); |
| | | if (index > -1) { |
| | | expandedRows.splice(index, 1); |
| | | } |
| | | } |
| | | } else { |
| | | expandedRowKeys.value = []; |
| | | } |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, ["contractAmount", "netContractAmount"]); |
| | | }; |
| | | const formatMoney = value => { |
| | | if (value === null || value === undefined || value === "") { |
| | | return "--"; |
| | | } |
| | | const num = Number(value); |
| | | return Number.isFinite(num) ? num.toFixed(2) : "--"; |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | | // ç¼è¾æ¶æ£æ¥å
¥åºç¶æï¼å®å
¨å
¥åºæ¶ä¸è½ç¼è¾ |
| | | if (type === "edit" && row) { |
| | | if (row.stockInStatus === "å®å
¨å
¥åº") { |
| | | proxy.$modal.msgWarning("å®å
¨å
¥åºç¶æçè®°å½ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | await getTemplateList(); |
| | | await userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | templateName.value = ""; |
| | | filterInputValue.value = ""; |
| | | isTemplateNameDuplicate.value = false; |
| | | try { |
| | | // å¹¶è¡å è½½åºç¡æ°æ® |
| | | const [salesRes, supplierRes] = await Promise.all([ |
| | | getSalesNo(), |
| | | getOptions(), |
| | | ]); |
| | | |
| | | salesContractList.value = salesRes || []; |
| | | // ä¾åºåè¿æ»¤åºisWhite=0 çæ°æ® |
| | | supplierList.value = (supplierRes.data || []).filter( |
| | | item => item.isWhite === 0 |
| | | ); |
| | | |
| | | form.value.entryDate = getCurrentDate(); |
| | | |
| | | if (type === "edit" && row?.id) { |
| | | // ç¼è¾æ¶å è½½æ°æ® |
| | | currentId.value = row.id; |
| | | try { |
| | | const purchaseRes = await getPurchaseById({ id: row.id, type: 2 }); |
| | | form.value = { ...purchaseRes, stockInStatus: row.stockInStatus }; |
| | | fileList.value = purchaseRes.storageBlobVOS || []; |
| | | // ä½¿ç¨ productList æ¥å£è·å产åå表ï¼ä»¥è·åå
¥åºå®¡æ ¸ç¶æ |
| | | const productRes = await productList({ |
| | | salesLedgerId: row.id, |
| | | type: 2, |
| | | }); |
| | | productData.value = productRes.data || []; |
| | | } catch (error) { |
| | | console.error("å è½½éè´å°è´¦æ°æ®å¤±è´¥:", error); |
| | | proxy.$modal.msgError("å è½½æ°æ®å¤±è´¥"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (form.value.salesLedgerId == -1) { |
| | | form.value.salesLedgerId = null; |
| | | } |
| | | console.log(form.value, "form.value==========="); |
| | | dialogFormVisible.value = true; |
| | | } catch (error) { |
| | | console.error("æå¼è¡¨å失败:", error); |
| | | proxy.$modal.msgError("å è½½åºç¡æ°æ®å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ åæ ¡æ£ |
| | | function handleBeforeUpload(file) { |
| | | // æ ¡æ£æä»¶å¤§å° |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶
è¿10MB!"); |
| | | return false; |
| | | } |
| | | proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); |
| | | return true; |
| | | } |
| | | |
| | | // ä¸ä¼ 失败 |
| | | function handleUploadError(err) { |
| | | proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | |
| | | // ä¸ä¼ æååè° |
| | | function handleUploadSuccess(res, file, uploadFiles) { |
| | | proxy.$modal.closeLoading(); |
| | | if (res.code === 200) { |
| | | file.tempId = res.data.tempId; |
| | | proxy.$modal.msgSuccess("ä¸ä¼ æå"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg); |
| | | proxy.$refs.fileUpload.handleRemove(file); |
| | | } |
| | | } |
| | | |
| | | // ç§»é¤æä»¶ |
| | | async function handleRemove(file) { |
| | | if (!file?.id) { |
| | | return; |
| | | } |
| | | console.log("handleRemove", file.id); |
| | | if (file.size > 1024 * 1024 * 10) { |
| | | // ä»
å端æ¸
çï¼ä¸è°ç¨å 餿¥å£åæç¤º |
| | | return; |
| | | } |
| | | |
| | | if (operationType.value === "edit" && file.id) { |
| | | try { |
| | | await delLedgerFile([file.id]); |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | } catch (error) { |
| | | console.error("å é¤æä»¶å¤±è´¥:", error); |
| | | proxy.$modal.msgError("å é¤æä»¶å¤±è´¥"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | // æ°å¢æ¶ï¼éè¦ä»æ¯ä¸ªäº§å对象ä¸å é¤ id åæ®µ |
| | | let processedProductData = productData.value; |
| | | if (operationType.value === "add") { |
| | | processedProductData = productData.value.map(product => { |
| | | const { id, ...rest } = product; |
| | | return rest; |
| | | }); |
| | | } |
| | | form.value.productData = proxy.HaveJson(processedProductData); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); |
| | | return; |
| | | } |
| | | form.value.storageBlobDTOS = fileList.value; |
| | | form.value.type = 2; |
| | | |
| | | // 妿salesLedgerId为空ï¼åä¸ä¼ ésalesContractNo |
| | | if (!form.value.salesLedgerId) { |
| | | form.value.salesContractNo = ""; |
| | | } |
| | | |
| | | // æ°å¢æ¶ä¸ä¼ éid |
| | | const submitData = { ...form.value }; |
| | | if (operationType.value === "add") { |
| | | delete submitData.id; |
| | | } |
| | | |
| | | // 妿éè´ååå·ä¸ºç©ºï¼åæ ¹æ®å½å
¥æ¥æèªå¨çæ |
| | | if (!submitData.purchaseContractNumber) { |
| | | try { |
| | | const purchaseNoRes = await createPurchaseNo(submitData.entryDate); |
| | | if (purchaseNoRes?.data) { |
| | | submitData.purchaseContractNumber = purchaseNoRes.data; |
| | | } |
| | | } catch (error) { |
| | | console.error("çæéè´ååå·å¤±è´¥:", error); |
| | | proxy.$modal.msgWarning("çæéè´ååå·å¤±è´¥"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | addOrEditPurchase(submitData).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // æå¼äº§åå¼¹æ¡ |
| | | const openProductForm = async (type, row, index) => { |
| | | // ç¼è¾æ¶æ£æ¥äº§åå
¥åºå®¡æ ¸ç¶æï¼å®å
¨å
¥åºæ¶ä¸è½ç¼è¾ |
| | | if (type === "edit" && row && row.stockInApprovalStatus === "å®å
¨å
¥åº") { |
| | | proxy.$modal.msgWarning("å®å
¨å
¥åºç¶æç产åä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | |
| | | productOperationType.value = type; |
| | | productOperationIndex.value = index; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | | productFormVisible.value = true; |
| | | |
| | | // å
è·å产åé项ï¼ç¡®ä¿æ°æ®å è½½å®æ |
| | | await getProductOptions(); |
| | | |
| | | // çå¾
DOM æ´æ° |
| | | await nextTick(); |
| | | |
| | | if (type === "add") { |
| | | productForm.value.isChecked = false; |
| | | } |
| | | |
| | | if (type === "edit") { |
| | | // å¤å¶è¡æ°æ® |
| | | productForm.value = { ...row }; |
| | | |
| | | // 妿æ¯ä»æ¨¡æ¿å è½½çæ°æ®ï¼å¯è½æ²¡æ productId å productModelId |
| | | // éè¦æ ¹æ® productCategory å specificationModel æ¥æ¥æ¾å¯¹åºç ID |
| | | if (!productForm.value.productId && productForm.value.productCategory) { |
| | | // æ ¹æ® productCategory æ¥æ¾ productId |
| | | const findProductIdByCategory = (nodes, categoryName) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].label === categoryName) { |
| | | return nodes[i].value; |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const found = findProductIdByCategory( |
| | | nodes[i].children, |
| | | categoryName |
| | | ); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const productId = findProductIdByCategory( |
| | | productOptions.value, |
| | | productForm.value.productCategory |
| | | ); |
| | | if (productId) { |
| | | productForm.value.productId = productId; |
| | | // è·ååå·å表并çå¾
宿 |
| | | const modelRes = await modelList({ id: productId }); |
| | | modelOptions.value = modelRes; |
| | | |
| | | // çå¾
DOM æ´æ° |
| | | await nextTick(); |
| | | |
| | | // æ ¹æ® specificationModel æ¥æ¾ productModelId |
| | | if ( |
| | | productForm.value.specificationModel && |
| | | modelOptions.value.length > 0 |
| | | ) { |
| | | const modelItem = modelOptions.value.find( |
| | | item => item.model === productForm.value.specificationModel |
| | | ); |
| | | if (modelItem) { |
| | | productForm.value.productModelId = modelItem.id; |
| | | // è®¾ç½®è§æ ¼åå·ååä½ |
| | | getProductModel(modelItem.id); |
| | | } |
| | | } |
| | | } |
| | | } else if (productForm.value.productId) { |
| | | // 妿æ productIdï¼æ£å¸¸å è½½åå·å表 |
| | | await getModels(productForm.value.productId); |
| | | |
| | | // çå¾
DOM æ´æ° |
| | | await nextTick(); |
| | | |
| | | if (productForm.value.productModelId) { |
| | | getProductModel(productForm.value.productModelId); |
| | | } |
| | | } |
| | | |
| | | // æååçå¾
䏿¬¡ DOM æ´æ°ï¼ç¡®ä¿æææ°æ®é½å·²è®¾ç½® |
| | | await nextTick(); |
| | | } |
| | | }; |
| | | const getProductOptions = () => { |
| | | return productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return res; |
| | | }); |
| | | }; |
| | | const getModels = value => { |
| | | if (value) { |
| | | productForm.value.productCategory = |
| | | findNodeById(productOptions.value, value) || ""; |
| | | return modelList({ id: value }).then(res => { |
| | | modelOptions.value = res; |
| | | return res; |
| | | }); |
| | | } else { |
| | | productForm.value.productCategory = ""; |
| | | modelOptions.value = []; |
| | | return Promise.resolve([]); |
| | | } |
| | | }; |
| | | const getProductModel = value => { |
| | | const index = modelOptions.value.findIndex(item => item.id === value); |
| | | if (index !== -1) { |
| | | productForm.value.specificationModel = modelOptions.value[index].model; |
| | | productForm.value.unit = modelOptions.value[index].unit; |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.unit = null; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å该èç¹çlabel |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // å¨åèç¹ä¸æ¾å°ï¼ç´æ¥è¿åï¼å·²ç»æ¯labelåç¬¦ä¸²ï¼ |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | |
| | | function convertIdToValue(data) { |
| | | return data.map(item => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // å° id æ¹ä¸º value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | |
| | | // æäº¤äº§å表å |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | | } else { |
| | | if (productOperationType.value === "add") { |
| | | productData.value.push({ ...productForm.value }); |
| | | console.log("productData.value---", productData.value); |
| | | } else { |
| | | productData.value[productOperationIndex.value] = { |
| | | ...productForm.value, |
| | | }; |
| | | } |
| | | closeProductDia(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const submitProductEdit = () => { |
| | | productForm.value.salesLedgerId = currentId.value; |
| | | productForm.value.type = 2; |
| | | addOrUpdateSalesLedgerProduct(productForm.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeProductDia(); |
| | | // ä½¿ç¨ productList æ¥å£å·æ°äº§åå表ï¼ä»¥è·åå
¥åºå®¡æ ¸ç¶æ |
| | | productList({ salesLedgerId: currentId.value, type: 2 }).then(res => { |
| | | productData.value = res.data || []; |
| | | }); |
| | | }); |
| | | }; |
| | | // å é¤äº§å |
| | | const deleteProduct = () => { |
| | | if (productData.value.length <= 1) { |
| | | proxy.$modal.msgWarning("éè´å°è´¦è³å°éè¦ä¿ç䏿¡äº§åæ°æ®"); |
| | | return; |
| | | } |
| | | if (productSelectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | // æ£æ¥éä¸ç产å䏿¯å¦æå®å
¨å
¥åºç |
| | | const hasFullyStocked = productSelectedRows.value.some( |
| | | row => row.stockInApprovalStatus === "å®å
¨å
¥åº" |
| | | ); |
| | | if (hasFullyStocked) { |
| | | proxy.$modal.msgWarning("éä¸ç产åä¸å
å«å®å
¨å
¥åºç产åï¼æ æ³å é¤"); |
| | | return; |
| | | } |
| | | if (operationType.value === "add") { |
| | | productData.value = productData.value.filter( |
| | | item => !productSelectedRows.value.includes(item) |
| | | ); |
| | | productSelectedRows.value = []; |
| | | } else { |
| | | let ids = []; |
| | | if (productSelectedRows.value.length > 0) { |
| | | ids = productSelectedRows.value.map(item => item.id); |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delProduct(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | closeProductDia(); |
| | | // ä½¿ç¨ productList æ¥å£å·æ°äº§åå表ï¼ä»¥è·åå
¥åºå®¡æ ¸ç¶æ |
| | | productList({ salesLedgerId: currentId.value, type: 2 }).then(res => { |
| | | productData.value = res.data || []; |
| | | }); |
| | | }); |
| | | await createAttachment({ |
| | | application: 'file', |
| | | recordType: RECORD_TYPE, |
| | | recordId, |
| | | storageBlobDTOs: blobs |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | } catch { |
| | | ElMessage.error('éä»¶ä¿å失败ï¼è¯·ç¨åéè¯') |
| | | } |
| | | } |
| | | }; |
| | | // å
³é产åå¼¹æ¡ |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | productFormVisible.value = false; |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/purchase/ledger/export", {}, "éè´å°è´¦.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value |
| | | .filter(item => item.salesLedgerId === null) |
| | | .map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPurchase(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchGenerate = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map((item) => item.id); |
| | | |
| | | ElMessageBox.confirm("确认æ¹éçææ°æ®ï¼", "æ¹éçæ", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.loading("æ£å¨æ¹éçææ°æ®ï¼è¯·ç¨å..."); |
| | | batchGeneratePurchaseInboundSteps({ ids }) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("æ¹éçææå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("æ¹éçæå¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | proxy.$modal.closeLoading(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | ElMessage.success('ä¿åæå') |
| | | formVisible.value = false |
| | | getList() |
| | | } finally { |
| | | saving.value = false |
| | | } |
| | | } |
| | | |
| | | const mathNum = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | if (!productForm.value.taxInclusiveUnitPrice) { |
| | | return; |
| | | } |
| | | if (!productForm.value.quantity) { |
| | | return; |
| | | } |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice, |
| | | productForm.value.quantity |
| | | ); |
| | | if (productForm.value.taxRate) { |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | productForm.value.taxExclusiveTotalPrice = |
| | | proxy.calculateTaxExclusiveTotalPrice( |
| | | productForm.value.taxInclusiveTotalPrice, |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | }; |
| | | const reverseMathNum = field => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请å
éæ©ç¨ç"); |
| | | return; |
| | | } |
| | | const taxRate = Number(productForm.value.taxRate); |
| | | if (!taxRate) return; |
| | | function openFileDialog(row) { |
| | | currentRowId.value = row.id |
| | | fileDialogVisible.value = true |
| | | } |
| | | |
| | | // ç¡®ä¿è¾å
¥å¼ä¸ä¸ºè´æ° |
| | | if ( |
| | | field === "taxInclusiveTotalPrice" || |
| | | field === "taxExclusiveTotalPrice" |
| | | ) { |
| | | const value = Number(productForm.value[field]); |
| | | if (value < 0) { |
| | | productForm.value[field] = "0"; |
| | | proxy.$modal.msgWarning("å¼ä¸è½å°äº0"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | if (field === "taxInclusiveTotalPrice") { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveUnitPrice) < 0) { |
| | | productForm.value.taxInclusiveUnitPrice = "0"; |
| | | } |
| | | } |
| | | // å·²ç¥å«ç¨æ»ä»·åå«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.taxInclusiveUnitPrice) |
| | | ).toFixed(3); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.quantity) < 0) { |
| | | productForm.value.quantity = "0"; |
| | | } |
| | | } |
| | | // åç®ä¸å«ç¨æ»ä»· |
| | | productForm.value.taxExclusiveTotalPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxExclusiveTotalPrice) < 0) { |
| | | productForm.value.taxExclusiveTotalPrice = "0"; |
| | | } |
| | | } else if (field === "taxExclusiveTotalPrice") { |
| | | // åç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = ( |
| | | Number(productForm.value.taxExclusiveTotalPrice) * |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveTotalPrice) < 0) { |
| | | productForm.value.taxInclusiveTotalPrice = "0"; |
| | | } |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveUnitPrice) < 0) { |
| | | productForm.value.taxInclusiveUnitPrice = "0"; |
| | | } |
| | | } |
| | | // å·²ç¥å«ç¨åä»·ï¼åç®æ°é |
| | | else if (productForm.value.taxInclusiveUnitPrice) { |
| | | productForm.value.quantity = ( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.taxInclusiveUnitPrice) |
| | | ).toFixed(3); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.quantity) < 0) { |
| | | productForm.value.quantity = "0"; |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | // éå®ååéæ©æ¹åæ¹æ³ |
| | | const salesLedgerChange = async row => { |
| | | console.log("row", row); |
| | | var index = salesContractList.value.findIndex(item => item.id == row); |
| | | console.log("index", index); |
| | | if (index > -1) { |
| | | await querygProductInfoByContractNo(); |
| | | } |
| | | }; |
| | | |
| | | const querygProductInfoByContractNo = async () => { |
| | | const { code, data } = await getProductInfoByContractNo({ |
| | | contractNo: form.value.salesLedgerId, |
| | | }); |
| | | if (code == 200) { |
| | | productData.value = data || []; |
| | | } |
| | | }; |
| | | |
| | | // è·å模æ¿ä¿¡æ¯ |
| | | const getTemplateList = async () => { |
| | | let res = await getPurchaseTemplateList(); |
| | | if (res && res.code === 200 && Array.isArray(res.data)) { |
| | | templateList.value = res.data; |
| | | } |
| | | }; |
| | | |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | // å 餿¨¡æ¿ |
| | | const handleDeleteTemplate = async item => { |
| | | if (!item.id) { |
| | | proxy.$modal.msgWarning("æ æ³å é¤è¯¥æ¨¡æ¿"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `ç¡®å®è¦å 餿¨¡æ¿"${item.templateName}"åï¼`, |
| | | "å é¤ç¡®è®¤", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ); |
| | | |
| | | const res = await delPurchaseTemplate([item.id]); |
| | | if (res && res.code === 200) { |
| | | ElMessage({ |
| | | message: "å 餿å", |
| | | type: "success", |
| | | }); |
| | | // 妿å é¤çæ¯å½åéä¸ç模æ¿ï¼æ¸
ç©ºéæ© |
| | | if (templateName.value === item.templateName) { |
| | | templateName.value = ""; |
| | | filterInputValue.value = ""; |
| | | } |
| | | // éæ°è·å模æ¿å表 |
| | | await getTemplateList(); |
| | | } else { |
| | | ElMessage({ |
| | | message: res?.msg || "å é¤å¤±è´¥", |
| | | type: "error", |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | if (error !== "cancel") { |
| | | console.error("å 餿¨¡æ¿å¤±è´¥:", error); |
| | | ElMessage({ |
| | | message: "å é¤å¤±è´¥ï¼è¯·ç¨åéè¯", |
| | | type: "error", |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | getTemplateList(); |
| | | }); |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .invalid-row { |
| | | opacity: 0.6; |
| | | background-color: #f5f7fa; |
| | | } |
| | | <style scoped> |
| | | .search_form { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .el-row { |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .toolbar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-bottom: 18px; |
| | | } |
| | | |
| | | .no-arrow-select { |
| | | --el-select-suffix-icon-color: transparent; /* éèé»è®¤ä¸æç®å¤´ */ |
| | | } |
| | | |
| | | .select-button-group { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | // 审æ¹äººèç¹å®¹å¨æ ·å¼ |
| | | .approver-nodes-container { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | padding: 16px; |
| | | background-color: #f8f9fa; |
| | | border-radius: 4px; |
| | | border: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | .approver-node-item { |
| | | flex: 0 0 calc(33.333% - 12px); |
| | | min-width: 200px; |
| | | padding: 12px; |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | border: 1px solid #dcdfe6; |
| | | transition: all 0.3s; |
| | | |
| | | &:hover { |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); |
| | | } |
| | | } |
| | | |
| | | .approver-node-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .approver-node-label { |
| | | font-size: 13px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .approver-node-item { |
| | | flex: 0 0 calc(50% - 8px); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .approver-node-item { |
| | | flex: 0 0 100%; |
| | | } |
| | | } |
| | | |
| | | // å é¤å¾æ æ ·å¼ |
| | | .delete-icon { |
| | | transition: all 0.3s; |
| | | |
| | | &:hover { |
| | | color: #f56c6c !important; |
| | | transform: scale(1.2); |
| | | } |
| | | } |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |
| | |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="客æ·åç§°" prop="customerName"> |
| | | <el-input v-model="form.customerName" placeholder="请è¾å
¥" clearable /> |
| | | <el-select |
| | | v-model="form.customerName" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | :disabled="isFromContract" |
| | | style="width: 100%" |
| | | @change="handleCustomerChange" |
| | | > |
| | | <el-option |
| | | v-for="item in customerOptions" |
| | | :key="item.id ?? item.customerName" |
| | | :label="item.customerName" |
| | | :value="item.customerName" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="ç«é¡¹æ¥æ" prop="setupDate"> |
| | | <el-date-picker |
| | | v-model="form.setupDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | <el-form-item label="项ç®åç±»" prop="projectClassification"> |
| | | <el-select |
| | | v-model="form.projectClassification" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | filterable |
| | | allow-create |
| | | default-first-option |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="c in projectClassificationOptions" |
| | | :key="c" |
| | | :label="c" |
| | | :value="c" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="é¡¹ç®æ¥æº" prop="projectSource"> |
| | | <el-input v-model="form.projectSource" placeholder="请è¾å
¥" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="ç«é¡¹äºº" prop="creatorName"> |
| | | <el-input v-model="form.creatorName" placeholder="请è¾å
¥" clearable /> |
| | | <el-select |
| | | v-model="form.projectSource" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | allow-create |
| | | default-first-option |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="æå¨æ·»å " value="æå¨æ·»å " /> |
| | | <el-option label="ä¸å¡å®¡æ¹" value="ä¸å¡å®¡æ¹" /> |
| | | <el-option label="å
¶ä»" value="å
¶ä»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="é¢è®¡å·¥æ(天)" prop="estimatedDays"> |
| | | <el-input-number v-model="form.estimatedDays" :min="0" controls-position="right" style="width: 100%" /> |
| | | <el-row v-if="isFromContract" :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ååéé¢(å
)"> |
| | | <el-input :model-value="form.projectAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="计åå¼å§æ¥æ" prop="planStartDate"> |
| | | <el-date-picker |
| | | v-model="form.planStartDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å忥æ"> |
| | | <el-input :model-value="form.orderDate" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="计åå®ææ¥æ" prop="planEndDate"> |
| | | <el-date-picker |
| | | v-model="form.planEndDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="项ç®ç±»å" prop="projectManagementPlanId"> |
| | | <el-select v-model="form.projectManagementPlanId" placeholder="è¯·éæ©" clearable style="width: 100%"> |
| | | <el-option v-for="opt in projectTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="项ç®éé¢" prop="projectAmount"> |
| | | <el-input-number v-model="form.projectAmount" :min="0" controls-position="right" style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="å®¡æ ¸ç¶æ" prop="auditStatus"> |
| | | <el-select v-model="form.auditStatus" placeholder="è¯·éæ©" clearable style="width: 100%"> |
| | | <el-option v-for="d in project_management" :key="d.value" :label="d.label" :value="d.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | </el-row> |
| | | <el-row :gutter="10" > |
| | | <el-col :span="24"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | :action="upload.url" |
| | | :headers="upload.headers" |
| | | multiple |
| | | :disabled="isView" |
| | | :before-upload="beforeUpload" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | name="files" |
| | | :on-remove="handleRemove" |
| | | > |
| | | <el-button type="primary" :disabled="isView">ä¸ä¼ æä»¶</el-button> |
| | | </el-upload> |
| | | <div v-if="existingAttachments.length > 0" class="attachment-list"> |
| | | <div |
| | | v-for="(att, idx) in existingAttachments" |
| | | :key="att.id || att.url || idx" |
| | | class="attachment-item" |
| | | > |
| | | <el-icon><Document /></el-icon> |
| | | <span class="attachment-name">{{ att.name || att.fileName || att.url || 'éä»¶' }}</span> |
| | | <el-button link type="primary" size="small" @click="downloadAttachment(att)">ä¸è½½</el-button> |
| | | </div> |
| | | </div> |
| | | <el-form-item label="éä»¶" prop="attachmentIds"> |
| | | <AttachmentUploadFile |
| | | v-model:file-list="fileList" |
| | | :disabled="isView" |
| | | :limit="10" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-row v-if="false" :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥" maxlength="100" show-word-limit /> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <div class="section-header" @click="toggleSection('product')"> |
| | | <div class="section-title"> |
| | | <span class="section-bar" /> |
| | | <span>产åä¿¡æ¯</span> |
| | | </div> |
| | | <div class="section-actions" @click.stop> |
| | | <el-button v-if="!isView" type="primary" @click="openProductForm('add')">æ·»å </el-button> |
| | | <el-button v-if="!isView" plain type="danger" @click="deleteProduct">å é¤</el-button> |
| | | <el-icon class="toggle-icon" @click="toggleSection('product')"> |
| | | <ArrowDown v-if="sectionCollapsed.product" /> |
| | | <ArrowUp v-else /> |
| | | </el-icon> |
| | | </div> |
| | | </div> |
| | | <div v-show="!sectionCollapsed.product" class="section-body"> |
| | | <el-table |
| | | :data="productData" |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeProductTable" |
| | | @selection-change="productSelected" |
| | | > |
| | | <el-table-column v-if="!isView" align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" /> |
| | | <el-table-column label="æ°é" prop="quantity" /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column v-if="!isView" fixed="right" label="æä½" min-width="60" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row, scope.$index)">ç¼è¾</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <div v-if="false" class="section"> |
| | | <div class="section-header" @click="toggleSection('team')"> |
| | | <div class="section-title"> |
| | | <span class="section-bar" /> |
| | |
| | | </div> |
| | | </div> --> |
| | | |
| | | <div class="section"> |
| | | <div v-if="false" class="section"> |
| | | <div class="section-header" @click="toggleSection('address')"> |
| | | <div class="section-title"> |
| | | <span class="section-bar" /> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <div v-if="false" class="section"> |
| | | <div class="section-header" @click="toggleSection('contact')"> |
| | | <div class="section-title"> |
| | | <span class="section-bar" /> |
| | |
| | | |
| | | <script setup name="ProjectManagementFormDia"> |
| | | import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue' |
| | | import { ArrowDown, ArrowUp, Delete, Plus, Document } from '@element-plus/icons-vue' |
| | | import { ArrowDown, ArrowUp, Delete, Plus } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { getToken } from '@/utils/auth' |
| | | import AttachmentUploadFile from '@/components/AttachmentUpload/file/index.vue' |
| | | import { listCustomer } from '@/api/basicData/customer.js' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import { listPlan } from '@/api/projectManagement/projectType' |
| | |
| | | const operationType = ref('add') |
| | | const formRef = ref() |
| | | const fileList = ref([]) |
| | | const existingAttachments = ref([]) |
| | | const upload = reactive({ |
| | | url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload', |
| | | headers: { Authorization: 'Bearer ' + getToken() } |
| | | }) |
| | | const customerOptions = ref([]) |
| | | |
| | | const projectTypeOptions = ref([]) |
| | | // 项ç®åç±»ï¼å
åæ»å 个ï¼åç»å¯åæ¢ä¸ºåå
¸ |
| | | const projectClassificationOptions = ['æ°åå转å', 'æºè½å¶é ', 'ç³»ç»éæ', 'å
¶ä»'] |
| | | const roleOptions = ref([]) |
| | | const userOptions = ref([]) |
| | | const productData = ref([]) |
| | |
| | | projectName: '', |
| | | customerName: '', |
| | | parentProjectName: '', |
| | | setupDate: '', |
| | | projectSource: '', |
| | | setupDate: todayStr(), |
| | | projectSource: 'æå¨æ·»å ', |
| | | projectClassification: '', |
| | | creatorName: '', |
| | | billStatus: '', |
| | | projectStage: '', |
| | |
| | | }) |
| | | |
| | | const isView = computed(() => operationType.value === 'view') |
| | | // ååçæç项ç®ï¼æ¥æºä¸ºä¸å¡å®¡æ¹ï¼ååä¿¡æ¯åªè¯»å±ç¤º |
| | | const isFromContract = computed(() => operationType.value !== 'add' && form.value.projectSource === 'ä¸å¡å®¡æ¹') |
| | | const dialogTitle = computed(() => { |
| | | if (operationType.value === 'add') return 'æ°å¢é¡¹ç®' |
| | | if (operationType.value === 'edit') return 'ç¼è¾é¡¹ç®' |
| | |
| | | sectionCollapsed[key] = !sectionCollapsed[key] |
| | | } |
| | | |
| | | function todayStr() { |
| | | const d = new Date() |
| | | const pad = n => String(n).padStart(2, '0') |
| | | return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` |
| | | } |
| | | |
| | | function resetFormData() { |
| | | Object.assign(form.value, { |
| | | id: undefined, |
| | |
| | | projectName: '', |
| | | customerName: '', |
| | | parentProjectName: '', |
| | | setupDate: '', |
| | | projectSource: '', |
| | | setupDate: todayStr(), |
| | | projectSource: 'æå¨æ·»å ', |
| | | projectClassification: '', |
| | | creatorName: '', |
| | | billStatus: '', |
| | | projectStage: '', |
| | |
| | | const res = await listPlan({ current: 1, size: 999 }) |
| | | const records = res?.data?.records || res?.records || res?.rows || [] |
| | | projectTypeOptions.value = records.map(item => ({ label: item.name, value: item.id })) |
| | | // æªæå®è®¡åæ¶ï¼é»è®¤ä½¿ç¨â项ç®ç³æ¥å
é¶æ®µâ计å |
| | | if (!form.value.projectManagementPlanId) { |
| | | const defaultPlan = records.find(item => item.name === '项ç®ç³æ¥å
é¶æ®µ') || records[0] |
| | | if (defaultPlan) form.value.projectManagementPlanId = defaultPlan.id |
| | | } |
| | | } catch { |
| | | projectTypeOptions.value = [] |
| | | } |
| | |
| | | } catch { |
| | | userOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | async function loadCustomerOptions() { |
| | | try { |
| | | const res = await listCustomer({ current: -1, size: -1, type: 0 }) |
| | | const records = res?.data?.records || res?.data || res?.records || [] |
| | | customerOptions.value = Array.isArray(records) ? records : [] |
| | | } catch { |
| | | customerOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | function handleCustomerChange(customerName) { |
| | | const customer = customerOptions.value.find(item => item.customerName === customerName) |
| | | form.value.clientId = customer?.id ?? null |
| | | } |
| | | |
| | | function addTeamRow() { |
| | |
| | | if (index > -1) form.value.addressList.splice(index, 1) |
| | | } |
| | | |
| | | function beforeUpload() { |
| | | if (isView.value) return false |
| | | proxy.$modal?.loading?.('æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å...') |
| | | return true |
| | | } |
| | | |
| | | function handleUploadError() { |
| | | proxy.$modal?.closeLoading?.() |
| | | ElMessage.error('ä¸ä¼ æä»¶å¤±è´¥') |
| | | } |
| | | |
| | | function handleUploadSuccess(res, file) { |
| | | console.log(res, file) |
| | | proxy.$modal?.closeLoading?.() |
| | | if (res?.code !== 200) { |
| | | ElMessage.error(res?.msg || 'ä¸ä¼ 失败') |
| | | return |
| | | } |
| | | const attachmentId = res?.data?.[0]?.id ?? "" |
| | | if (!attachmentId) return |
| | | form.value.attachmentIds.push(attachmentId) |
| | | console.log(form.value.attachmentIds) |
| | | ElMessage.success('ä¸ä¼ æå') |
| | | } |
| | | |
| | | function handleRemove(file) { |
| | | const attachmentId = file?.attachmentId |
| | | if (!attachmentId) return |
| | | form.value.attachmentIds = (form.value.attachmentIds || []).filter(id => id !== attachmentId) |
| | | } |
| | | |
| | | async function openDialog(payload = {}) { |
| | | operationType.value = payload.operationType || 'add' |
| | | resetFormData() |
| | | await Promise.all([loadProjectTypeOptions(), loadRoleOptions(), loadUserOptions(), getProductOptions()]) |
| | | await Promise.all([loadProjectTypeOptions(), loadRoleOptions(), loadUserOptions(), loadCustomerOptions(), getProductOptions()]) |
| | | if (payload.row?.id) { |
| | | try { |
| | | const res = await getProject(payload.row.id) |
| | |
| | | estimatedDays: Number(info.estimatedDays) || computeEstimatedDays(info.planStartTime, info.planEndTime) || 0, |
| | | projectName: info.title ?? '', |
| | | customerName: info.clientName ?? '', |
| | | clientId: info.clientId ?? undefined, |
| | | parentProjectName: info.projectManagementInfoParentName ?? '', |
| | | setupDate: info.establishTime ?? '', |
| | | projectSource: info.source ?? '', |
| | | projectClassification: info.projectClassification ?? '', |
| | | creatorName: info.managerName ?? '', |
| | | billStatus: normalizeDictValue(info.status), |
| | | projectStage: normalizeDictValue(info.stage ?? info.projectStage), |
| | | planStartDate: info.planStartTime ?? '', |
| | | planEndDate: info.planEndTime ?? '', |
| | | projectAmount: info.orderAmount ?? 0, |
| | | orderDate: info.orderDate ?? '', |
| | | auditStatus: normalizeDictValue(info.reviewStatus), |
| | | remark: info.remark ?? '', |
| | | attachmentIds: Array.isArray(info.attachmentIds) ? info.attachmentIds : [], |
| | |
| | | contactRemark: contractInfo.rappresentanteLegale ?? '' |
| | | }) |
| | | |
| | | existingAttachments.value = Array.isArray(info.attachmentList) |
| | | fileList.value = Array.isArray(info.attachmentList) |
| | | ? info.attachmentList.map(a => ({ |
| | | id: a.id ?? a.fileId, |
| | | name: a.fileName ?? a.name, |
| | |
| | | return |
| | | } |
| | | await formRef.value?.validate?.() |
| | | if (!productData.value || productData.value.length === 0) { |
| | | proxy.$modal?.msgWarning?.('请添å 产åä¿¡æ¯') |
| | | return |
| | | } |
| | | const findLabel = (list, value) => (list || []).find(i => String(i.value) === String(value))?.label |
| | | const teamList = (form.value.teamList || []).map(t => ({ |
| | | userId: t.memberId, |
| | | userName: findLabel(userOptions.value, t.memberId), |
| | | userRoleId: t.roleId, |
| | | userRoleName: findLabel(roleOptions.value, t.roleId), |
| | | joinTime: t.enterDate, |
| | | departTime: t.leaveDate, |
| | | contact: t.phone, |
| | | remark: t.remark |
| | | })) |
| | | |
| | | const shippingRow = (form.value.addressList || [])[0] || {} |
| | | const shippingAddress = { |
| | | id: undefined, |
| | | consignee: shippingRow.receiver, |
| | | contract: shippingRow.phone, |
| | | address: shippingRow.address |
| | | } |
| | | |
| | | const contractInfo = { |
| | | id: undefined, |
| | | name: form.value.contactName, |
| | | sex: form.value.contactGender === '1' ? 'ç·' : form.value.contactGender === '2' ? '女' : '', |
| | | birthday: form.value.contactBirthday, |
| | | department: form.value.contactDept, |
| | | job: form.value.contactJob, |
| | | phoneNumber: form.value.contactMobile, |
| | | email: form.value.contactEmail, |
| | | qq: form.value.contactQq, |
| | | lineaFissa: form.value.contactWorkWechat, |
| | | wx: form.value.contactWechat, |
| | | origineEtnica: form.value.contactAddress, |
| | | rappresentanteLegale: form.value.contactRemark |
| | | } |
| | | const info = { |
| | | id: form.value.id ?? null, |
| | | no: form.value.billNo, |
| | |
| | | clientName: form.value.customerName, |
| | | projectManagementInfoParentId: form.value.parentProjectId ?? null, |
| | | projectManagementPlanId: form.value.projectManagementPlanId ?? null, |
| | | establishTime: form.value.setupDate, |
| | | establishTime: form.value.setupDate || todayStr(), |
| | | source: form.value.projectSource, |
| | | projectClassification: form.value.projectClassification, |
| | | managerId: form.value.managerId ?? null, |
| | | managerName: form.value.creatorName, |
| | | salesmanId: form.value.salesmanId ?? null, |
| | |
| | | reviewStatus: form.value.auditStatus === '' || form.value.auditStatus === undefined || form.value.auditStatus === null ? null : Number(form.value.auditStatus), |
| | | stage: form.value.projectStage === '' || form.value.projectStage === undefined || form.value.projectStage === null ? null : Number(form.value.projectStage), |
| | | remark: form.value.remark, |
| | | attachmentIds: Array.isArray(form.value.attachmentIds) ? form.value.attachmentIds : [], |
| | | teamList |
| | | attachmentIds: (fileList.value || []) |
| | | .map(f => (f?.id ?? f?.rawData?.id)) |
| | | .filter(v => v !== undefined && v !== null && v !== '') |
| | | .map(String) |
| | | } |
| | | |
| | | const payload = { |
| | | info, |
| | | shippingAddress, |
| | | contractInfo, |
| | | salesLedgerProductList: productData.value |
| | | shippingAddress: null, |
| | | contractInfo: null, |
| | | salesLedgerProductList: null |
| | | } |
| | | |
| | | const req = operationType.value === 'edit' ? updateProject : addProject |
| | |
| | | setupDate: r.establishTime ?? r.setupDate, |
| | | projectType: r.planName ?? r.projectType, |
| | | projectSource: r.source ?? r.projectSource, |
| | | projectClassification: r.departmentName ?? r.projectClassification, |
| | | projectClassification: r.projectClassification, |
| | | raw: r |
| | | })) |
| | | pagination.value.total = response?.total || response?.data?.total || 0 |
| | |
| | | <div class="header-title">项ç®è¯¦æ
</div> |
| | | <div class="header-actions"> |
| | | <el-button style="color: white;background: #002FA7;" @click="openProgressReport">è¿åº¦æ±æ¥</el-button> |
| | | <!-- <el-button type="danger" @click="openDiscussProgress">æ´½è°è¿åº¦</el-button> --> |
| | | <el-button @click="goBack">è¿å</el-button> |
| | | </div> |
| | | </div> |
| | | <el-steps v-if="steps.length > 0" :active="activeStep" align-center finish-status="success"> |
| | | <el-step v-for="(s, idx) in steps" :key="idx" :title="s" /> |
| | | </el-steps> |
| | | <div v-if="stageSteps.length > 0" class="progress-block"> |
| | | <el-steps :active="activeStep" align-center finish-status="success"> |
| | | <el-step v-for="(s, idx) in stageSteps" :key="idx" :title="s.name" /> |
| | | </el-steps> |
| | | <div class="progress-percent"> |
| | | <span class="progress-label">æ´ä½è¿åº¦</span> |
| | | <el-progress :percentage="overallProgress" :stroke-width="12" style="flex: 1" /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <el-card class="content-card" shadow="never" v-loading="loading"> |
| | | <el-tabs v-model="activeTab"> |
| | | <el-tab-pane label="åºç¡èµæ" name="base"> |
| | | <el-descriptions :column="4" border> |
| | | <el-descriptions-item label="项ç®ID">{{ info.id ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ®ç¼å·">{{ info.no || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç§°">{{ info.title || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ info.clientName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·ID">{{ info.clientId ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç±»">{{ info.projectClassification || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶é¡¹ç®">{{ parentProjectLabel }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®ç±»å">{{ projectTypeLabel }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç«é¡¹æ¥æ">{{ info.establishTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¡¹ç®æ¥æº">{{ info.source || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç«é¡¹äºº">{{ info.managerName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ info.salesmanName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¨é¨">{{ info.departmentName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ®ç¶æ"> |
| | | <dict-tag :options="bill_status" :value="String(info.status ?? '')" /> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å½åé¶æ®µ">{{ currentStageName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®¡æ ¸ç¶æ"> |
| | | <dict-tag :options="project_management" :value="String(info.reviewStatus ?? '')" /> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="计åç¶æ"> |
| | | <dict-tag :options="plan_status" :value="String(info.stage ?? '')" /> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="é¢è®¡å·¥æ(天)">{{ estimatedDays }}</el-descriptions-item> |
| | | <el-descriptions-item label="计åå¼å§æ¥æ">{{ info.planStartTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="计åå®ææ¥æ">{{ info.planEndTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®é
å¼å§æ¥æ">{{ info.actualStartTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®é
å®ææ¥æ">{{ info.actualEndTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="è®¢åæ¥æ">{{ info.orderDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®éé¢">{{ info.orderAmount ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="4">{{ info.remark || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <el-descriptions v-if="isFromContract" :column="3" border style="margin-top: 14px;"> |
| | | <el-descriptions-item label="åå客æ·">{{ info.clientName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ååéé¢(å
)">{{ info.orderAmount ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å忥æ">{{ info.orderDate || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="attachment-block" v-if="attachments.length > 0"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-divider content-position="left">产åä¿¡æ¯</el-divider> |
| | | <el-table :data="productRows" border show-summary :summary-method="summarizeProductTable"> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="产å大类" prop="productCategory" show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" width="90" /> |
| | | <el-table-column label="æ°é" prop="quantity" width="90" /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="90" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column label="ä¸å«ç¨æ»ä»·(å
)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column label="å票类å" prop="invoiceType" width="110" /> |
| | | </el-table> |
| | | |
| | | <el-divider content-position="left">项ç®å¢é</el-divider> |
| | | <el-table :data="teamRows" border> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column label="å§å" prop="userName" show-overflow-tooltip /> |
| | | <el-table-column label="项ç®ç»è§è²" prop="userRoleName" show-overflow-tooltip /> |
| | | <el-table-column label="è¿å
¥æ¥æ" prop="joinTime" width="140" /> |
| | | <el-table-column label="ç¦»å¼æ¥æ" prop="departTime" width="140" /> |
| | | <el-table-column label="èç³»æ¹å¼" prop="contact" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | </el-table> |
| | | |
| | | <el-divider content-position="left">æ¶è´§å°å</el-divider> |
| | | <el-descriptions :column="3" border> |
| | | <el-descriptions-item label="æ¶è´§äºº">{{ shippingAddress.consignee || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="èç³»æ¹å¼">{{ shippingAddress.contract || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å°å">{{ shippingAddress.address || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <el-divider content-position="left">è系信æ¯</el-divider> |
| | | <el-descriptions :column="4" border> |
| | | <el-descriptions-item label="è系人">{{ contractInfo.name || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ§å«">{{ contractInfo.sex || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="çæ¥">{{ contractInfo.birthday || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="é¨é¨">{{ contractInfo.department || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="èå¡">{{ contractInfo.job || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ææºå·">{{ contractInfo.phoneNumber || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="é®ç®±">{{ contractInfo.email || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="QQ">{{ contractInfo.qq || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åºå®çµè¯">{{ contractInfo.lineaFissa || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="微信">{{ contractInfo.wx || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç±è´¯">{{ contractInfo.origineEtnica || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ³äººä»£è¡¨">{{ contractInfo.rappresentanteLegale || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="项ç®ç±»å" name="plan"> |
| | | <el-descriptions :column="4" border> |
| | | <el-descriptions-item label="ç±»ååç§°">{{ projectPlan.name || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="3">{{ projectPlan.description || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="attachment-block" v-if="planAttachments.length > 0"> |
| | | <div class="attachment-title">ç±»åéä»¶</div> |
| | | <div class="attachment-list"> |
| | | <div v-for="(att, idx) in planAttachments" :key="att.id || att.url || idx" class="attachment-item"> |
| | | <el-icon><Document /></el-icon> |
| | | <span class="attachment-name">{{ att.name || att.fileName || att.url || 'éä»¶' }}</span> |
| | | <el-button link type="primary" size="small" @click="downloadAttachment(att)">ä¸è½½</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="planNodeRows" border style="margin-top: 14px;"> |
| | | <el-table-column align="center" label="æ¥éª¤" type="index" width="80" /> |
| | | <el-table-column label="é¶æ®µåç§°" prop="name" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="è´è´£äºº" prop="leaderName" width="140" show-overflow-tooltip /> |
| | | <el-table-column label="é¢è®¡å·¥æ(天)" prop="estimatedDuration" width="140" /> |
| | | <el-table-column label="å·¥æ¶åä»·" prop="hourlyRate" width="120" /> |
| | | <el-table-column label="ä½ä¸å
容" prop="workContent" min-width="180" show-overflow-tooltip /> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="项ç®é¶æ®µ" name="stage"> |
| | |
| | | <el-table-column label="计åç»æ" prop="planEndTime" width="120" /> |
| | | <el-table-column label="å®é
å¼å§" prop="actualStartTime" width="120" /> |
| | | <el-table-column label="å®é
ç»æ" prop="actualEndTime" width="120" /> |
| | | <el-table-column label="é¢è®¡å·¥æ(天)" prop="estimatedDuration" width="130" /> |
| | | <el-table-column label="æ¬æ¬¡è¿åº¦æ¥æ" prop="reportDate" width="130" /> |
| | | <el-table-column label="éä»¶" width="90" align="center"> |
| | | <template #default="{ row }"> |
| | | <span>{{ row.attachmentCount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column v-if="false" label="æä½" width="100" align="center" fixed="right" > |
| | | <el-table-column v-if="false" label="æä½" width="100" align="center" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="danger" size="small" @click="handleDeleteStage(row)">å é¤</el-button> |
| | | </template> |
| | |
| | | :project-id="info.id" |
| | | :project-info="info" |
| | | :plan-nodes="planNodeRows" |
| | | :stage-records="stageNodeRows" |
| | | :default-plan-node-id="defaultPlanNodeId" |
| | | @submitted="handleProgressSubmitted" |
| | | /> |
| | | <DiscussProgressDialog |
| | | v-model="discussProgressVisible" |
| | | :project-id="info.id" |
| | | :plan-nodes="planNodeRows" |
| | | :default-plan-node-id="defaultPlanNodeId" |
| | | @submitted="handleDiscussSubmitted" |
| | | /> |
| | | </template> |
| | | |
| | |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { Document } from '@element-plus/icons-vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { getProject, saveStage, listStage, deleteStage } from '@/api/projectManagement/project' |
| | | import { getProject, saveStage, listStage, deleteStage, updateProject } from '@/api/projectManagement/project' |
| | | import { listPlan } from '@/api/projectManagement/projectType' |
| | | import ProgressReportDialog from '@/components/ProjectManagement/ProgressReportDialog.vue' |
| | | import DiscussProgressDialog from '@/components/ProjectManagement/DiscussProgressDialog.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const { bill_status, project_management, plan_status } = proxy.useDict('bill_status', 'project_management', 'plan_status') |
| | | const { project_management } = proxy.useDict('project_management') |
| | | |
| | | const loading = ref(false) |
| | | const activeTab = ref('base') |
| | | const progressReportVisible = ref(false) |
| | | const discussProgressVisible = ref(false) |
| | | const userStore = useUserStore() |
| | | |
| | | const info = ref({}) |
| | | const shippingAddress = ref({}) |
| | | const contractInfo = ref({}) |
| | | const productRows = ref([]) |
| | | const teamRows = ref([]) |
| | | const attachments = ref([]) |
| | | const projectTypeMap = ref(new Map()) |
| | | const projectPlan = ref({}) |
| | | const planNodeRows = ref([]) |
| | | const planAttachments = ref([]) |
| | | const stageNodeRows = ref([]) |
| | | |
| | | const estimatedDays = computed(() => { |
| | | const raw = info.value?.estimatedDays |
| | | const n = Number(raw) |
| | | if (Number.isFinite(n) && n > 0) return n |
| | | const start = info.value?.planStartTime |
| | | const end = info.value?.planEndTime |
| | | if (!start || !end) return 0 |
| | | const startTime = new Date(`${start}T00:00:00`).getTime() |
| | | const endTime = new Date(`${end}T00:00:00`).getTime() |
| | | if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime < startTime) return 0 |
| | | return Math.floor((endTime - startTime) / (24 * 60 * 60 * 1000)) + 1 |
| | | }) |
| | | |
| | | const projectTypeLabel = computed(() => { |
| | | const id = info.value?.projectManagementPlanId |
| | | if (id === undefined || id === null || id === '') return '-' |
| | | const p = projectTypeMap.value.get(Number(id)) |
| | | return p?.name || String(id) |
| | | }) |
| | | const isFromContract = computed(() => info.value?.source === 'ä¸å¡å®¡æ¹') |
| | | |
| | | const parentProjectLabel = computed(() => { |
| | | return ( |
| | |
| | | ) |
| | | }) |
| | | |
| | | const planStageEnum = computed(() => { |
| | | const list = Array.isArray(plan_status) ? plan_status : [] |
| | | return list.map(i => ({ value: String(i.value), label: i.label })) |
| | | // åé¶æ®µå½åè¿åº¦ï¼åä¸é¶æ®µå¤æ¬¡æ±æ¥åæå¤§å¼ï¼ |
| | | const stageProgressMap = computed(() => { |
| | | const map = {} |
| | | stageNodeRows.value.forEach(r => { |
| | | const key = String(r.projectManagementPlanNodeId ?? '') |
| | | if (!key) return |
| | | map[key] = Math.max(map[key] || 0, Number(r.progress) || 0) |
| | | }) |
| | | return map |
| | | }) |
| | | |
| | | const steps = computed(() => { |
| | | const nodes = Array.isArray(planNodeRows.value) ? planNodeRows.value : [] |
| | | const sorted = [...nodes].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | const labels = sorted.map(i => i.name || i.workContent).filter(Boolean) |
| | | return labels.length > 0 ? labels : planStageEnum.value.map(i => i.label) |
| | | // é¶æ®µè¿åº¦æ¡æ°æ®ï¼ä¼å
使ç¨é¡¹ç®ä¸åæ¥ç planStage |
| | | const stageSteps = computed(() => { |
| | | const planStages = Array.isArray(info.value?.planStage) ? info.value.planStage : [] |
| | | if (planStages.length > 0) { |
| | | return [...planStages] |
| | | .sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | .map(i => ({ |
| | | id: i.id, |
| | | name: i.name || String(i.id ?? ''), |
| | | status: Number(i.planStageEnum) || 1, |
| | | progress: stageProgressMap.value[String(i.id)] ?? 0 |
| | | })) |
| | | } |
| | | const nodes = [...(planNodeRows.value || [])].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | return nodes.map(n => { |
| | | const progress = stageProgressMap.value[String(n.id)] ?? 0 |
| | | return { |
| | | id: n.id, |
| | | name: n.name || n.workContent || String(n.id ?? ''), |
| | | status: progress >= 100 ? 3 : progress > 0 ? 2 : 1, |
| | | progress |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const activeStep = computed(() => { |
| | | const statusOrStage = info.value?.stage ?? info.value?.status |
| | | const enumList = planStageEnum.value |
| | | // ä¼å
ä½¿ç¨ planStageEnum ç value å¹é
|
| | | const found = enumList.find(i => i.value === String(statusOrStage)) |
| | | const label = found?.label |
| | | // å¨é¡¹ç®ç±»åèç¹ä¸æ¥æ¾å¯¹åº label ç䏿 |
| | | const nodeLabels = steps.value |
| | | const idxByLabel = label ? nodeLabels.findIndex(l => String(l) === String(label)) : -1 |
| | | if (idxByLabel >= 0) return idxByLabel + 1 |
| | | // åéï¼å¦æ statusOrStage æ¯æ°åç´¢å¼ |
| | | const n = Number(statusOrStage) |
| | | if (Number.isFinite(n) && n > 0) return Math.min(n, nodeLabels.length) |
| | | return 0 |
| | | const idx = stageSteps.value.findIndex(s => s.status !== 3) |
| | | return idx === -1 ? stageSteps.value.length : idx |
| | | }) |
| | | |
| | | const currentStageName = computed(() => { |
| | | const current = stageSteps.value.find(s => s.status === 2) || stageSteps.value[activeStep.value] |
| | | return current?.name || '-' |
| | | }) |
| | | |
| | | const overallProgress = computed(() => { |
| | | const list = stageSteps.value |
| | | if (list.length === 0) return 0 |
| | | const sum = list.reduce((acc, s) => acc + (Number(s.progress) || 0), 0) |
| | | return Math.round(sum / list.length) |
| | | }) |
| | | |
| | | const defaultPlanNodeId = computed(() => { |
| | | const next = stageSteps.value.find(s => s.status !== 3) |
| | | return next?.id ?? stageSteps.value[stageSteps.value.length - 1]?.id |
| | | }) |
| | | |
| | | function goBack() { |
| | |
| | | progressReportVisible.value = true |
| | | } |
| | | |
| | | function openDiscussProgress() { |
| | | discussProgressVisible.value = true |
| | | async function saveTeamIfNeeded(teamList) { |
| | | if (!Array.isArray(teamList)) return |
| | | const attachmentIds = attachments.value |
| | | .map(a => a.id ?? a.fileId) |
| | | .filter(v => v !== undefined && v !== null && v !== '') |
| | | .map(String) |
| | | const res = await updateProject({ |
| | | info: { |
| | | id: info.value?.id, |
| | | no: info.value?.no, |
| | | title: info.value?.title, |
| | | projectManagementPlanId: info.value?.projectManagementPlanId, |
| | | teamList, |
| | | attachmentIds |
| | | } |
| | | }) |
| | | if (res?.code !== 200) throw new Error(res?.msg || 'å¢éä¿å失败') |
| | | } |
| | | |
| | | const defaultPlanNodeId = computed(() => { |
| | | const nodes = Array.isArray(planNodeRows.value) ? planNodeRows.value : [] |
| | | if (nodes.length === 0) return undefined |
| | | const stageVal = info.value?.stage |
| | | const direct = nodes.find(n => String(n.id) === String(stageVal)) |
| | | if (direct?.id) return direct.id |
| | | const sorted = [...nodes].sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0)) |
| | | const idx = Number(stageVal) |
| | | if (Number.isFinite(idx)) { |
| | | const byIndex = sorted[idx - 1] || sorted[idx] || sorted[0] |
| | | if (byIndex?.id) return byIndex.id |
| | | } |
| | | return sorted[0]?.id |
| | | }) |
| | | |
| | | async function handleProgressSubmitted(payload) { |
| | | try { |
| | | if (Array.isArray(payload.teamList)) { |
| | | await saveTeamIfNeeded(payload.teamList) |
| | | } |
| | | const nodes = Array.isArray(planNodeRows.value) ? planNodeRows.value : [] |
| | | const node = nodes.find(n => String(n.id) === String(payload.planNodeId)) || {} |
| | | const description = payload.remark |
| | |
| | | projectManagementInfoId: info.value?.id, |
| | | description, |
| | | actualLeaderId: userStore.id || info.value?.managerId, |
| | | actualLeaderName: userStore.nickName || info.value?.managerName || info.value?.managerName, |
| | | actualLeaderName: userStore.nickName || info.value?.managerName || '', |
| | | estimatedDuration: Number(node.estimatedDuration ?? 0) || 0, |
| | | planStartTime: payload.planStartTime || info.value?.planStartTime, |
| | | planEndTime: payload.planEndTime || info.value?.planEndTime, |
| | | actualStartTime: payload.actualStartTime || null, |
| | | actualEndTime: payload.actualEndTime || null, |
| | | progress: Number(payload.totalProgress ?? payload.completionProgress ?? 0) || 0, |
| | | progress: Number(payload.completionProgress ?? payload.totalProgress ?? 0) || 0, |
| | | storageBlobDTOs: Array.isArray(payload.storageBlobDTOs) ? payload.storageBlobDTOs : [] |
| | | } |
| | | const res = await saveStage(req) |
| | |
| | | } |
| | | ElMessage.error(res?.msg || 'æäº¤å¤±è´¥') |
| | | } catch (e) { |
| | | ElMessage.error('æäº¤å¤±è´¥') |
| | | } |
| | | } |
| | | |
| | | async function handleDiscussSubmitted(payload) { |
| | | try { |
| | | const nodes = Array.isArray(planNodeRows.value) ? planNodeRows.value : [] |
| | | const node = nodes.find(n => String(n.id) === String(payload.planNodeId)) || {} |
| | | const req = { |
| | | id: null, |
| | | projectManagementPlanNodeId: payload.planNodeId, |
| | | projectManagementInfoId: info.value?.id, |
| | | description: payload.remark, |
| | | actualLeaderId: userStore.id || info.value?.managerId, |
| | | actualLeaderName: userStore.nickName || info.value?.managerName || info.value?.managerName, |
| | | estimatedDuration: Number(node.estimatedDuration ?? 0) || 0, |
| | | planStartTime: info.value?.planStartTime, |
| | | planEndTime: info.value?.planEndTime, |
| | | actualStartTime: info.value?.actualStartTime || null, |
| | | actualEndTime: info.value?.actualEndTime || null, |
| | | progress: Number(info.value?.progress ?? 0) || 0, |
| | | attachmentIds: Array.isArray(payload.attachmentIds) ? payload.attachmentIds : [] |
| | | } |
| | | const res = await saveStage(req) |
| | | if (res?.code === 200) { |
| | | ElMessage.success('æäº¤æå') |
| | | await Promise.all([loadDetail(), loadStageList()]) |
| | | return |
| | | } |
| | | ElMessage.error(res?.msg || 'æäº¤å¤±è´¥') |
| | | } catch (e) { |
| | | ElMessage.error('æäº¤å¤±è´¥') |
| | | ElMessage.error(e?.message || 'æäº¤å¤±è´¥') |
| | | } |
| | | } |
| | | |
| | | function downloadAttachment(row) { |
| | | if (row?.url) { |
| | | try { |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | proxy.$download.byUrl(row.url, row.originalFilename) |
| | | return |
| | | } catch (e) {} |
| | | } |
| | |
| | | const records = Array.isArray(list) ? list : [] |
| | | stageNodeRows.value = records.map(r => { |
| | | const attachmentList = Array.isArray(r.attachmentList) ? r.attachmentList : [] |
| | | const attachmentIds = Array.isArray(r.attachmentIds) ? r.attachmentIds : [] |
| | | const blobList = Array.isArray(r.storageBlobVOs) ? r.storageBlobVOs : [] |
| | | return { |
| | | ...r, |
| | | stageName: getPlanNodeName(r.projectManagementPlanNodeId), |
| | | attachmentCount: attachmentList.length || attachmentIds.length || 0 |
| | | reportDate: r.createTime ? String(r.createTime).slice(0, 10) : '', |
| | | attachmentCount: attachmentList.length || blobList.length || 0 |
| | | } |
| | | }) |
| | | } catch { |
| | |
| | | function syncProjectPlan() { |
| | | const id = info.value?.projectManagementPlanId |
| | | if (id === undefined || id === null || id === '') { |
| | | projectPlan.value = {} |
| | | planNodeRows.value = [] |
| | | planAttachments.value = [] |
| | | return |
| | | } |
| | | const plan = projectTypeMap.value.get(Number(id)) || {} |
| | | projectPlan.value = plan || {} |
| | | planNodeRows.value = Array.isArray(plan?.planNodeList) ? plan.planNodeList : [] |
| | | planAttachments.value = Array.isArray(plan?.attachmentList) ? plan.attachmentList : [] |
| | | } |
| | | |
| | | async function loadDetail() { |
| | |
| | | const res = await getProject(id) |
| | | const detail = res?.data?.data ?? res?.data ?? res |
| | | info.value = detail?.info || {} |
| | | shippingAddress.value = detail?.shippingAddress || {} |
| | | contractInfo.value = detail?.contractInfo || {} |
| | | productRows.value = Array.isArray(detail?.salesLedgerProductList) ? detail.salesLedgerProductList : [] |
| | | teamRows.value = Array.isArray(detail?.info?.teamList) ? detail.info.teamList : [] |
| | | attachments.value = Array.isArray(detail?.info?.attachmentList) ? detail.info.attachmentList : [] |
| | | syncProjectPlan() |
| | | await loadStageList() |
| | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .section-bar { |
| | | width: 3px; |
| | | height: 14px; |
| | | background: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | .header-card { |
| | | margin-bottom: 14px; |
| | | } |
| | |
| | | .header-title { |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .progress-block { |
| | | padding-top: 6px; |
| | | } |
| | | |
| | | .progress-percent { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .progress-label { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .content-card { |
| | |
| | | white-space: nowrap; |
| | | } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | | style="width: 220px; margin-right: 10px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | @keyup.enter="handleQuery" /> |
| | | <span class="search_title">æ¥è¡¨å¹´åº¦ï¼</span> |
| | | <el-select v-model="searchForm.reportYear" |
| | | style="width: 150px; margin-right: 10px" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | @change="handleQuery"> |
| | | <el-option v-for="year in yearOptions" |
| | | :key="year" |
| | | :label="year + 'å¹´'" |
| | | :value="year" /> |
| | | </el-select> |
| | | <span class="search_title">审æ¹ç¶æï¼</span> |
| | | <el-select v-model="searchForm.auditStatus" |
| | | style="width: 150px" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | @change="handleQuery"> |
| | | <el-option label="å¾
审æ¹" |
| | | :value="0" /> |
| | | <el-option label="å·²éè¿" |
| | | :value="1" /> |
| | | <el-option label="å·²éå" |
| | | :value="2" /> |
| | | </el-select> |
| | | <el-button type="primary" |
| | | style="margin-left: 10px" |
| | | @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table v-loading="loading" |
| | | :data="pagedData" |
| | | border |
| | | size="small" |
| | | style="margin-top: 10px"> |
| | | <el-table-column prop="customerName" |
| | | label="客æ·åç§°" |
| | | min-width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="reportYear" |
| | | label="年度" |
| | | width="80" |
| | | align="center" /> |
| | | <el-table-column prop="reportName" |
| | | label="æ¥è¡¨åç§°" |
| | | min-width="160" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æ¥è¡¨éä»¶" |
| | | min-width="240"> |
| | | <template #default="scope"> |
| | | <FileUpload v-model="scope.row.reportFiles" |
| | | disabled |
| | | :is-show-tip="false" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="uploadUserName" |
| | | label="æäº¤äºº" |
| | | width="100" /> |
| | | <el-table-column prop="updateTime" |
| | | label="æäº¤æ¶é´" |
| | | width="160" /> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="statusTagType(scope.row.auditStatus)"> |
| | | {{ statusText(scope.row.auditStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="auditUserName" |
| | | label="审æ¹äºº" |
| | | width="100" /> |
| | | <el-table-column prop="auditRemark" |
| | | label="å®¡æ¹æè§" |
| | | min-width="160" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æä½" |
| | | width="130" |
| | | align="center" |
| | | fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" |
| | | link |
| | | v-hasPermi="['sales:report:audit']" |
| | | @click="openAuditDialog(scope.row)">审æ¹</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div v-if="total > 0" |
| | | class="pagination-wrap"> |
| | | <el-pagination v-model:current-page="page.current" |
| | | v-model:page-size="page.size" |
| | | :page-sizes="[10, 20, 50]" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" /> |
| | | </div> |
| | | <div v-else-if="!loading" |
| | | class="no-record">ææ å®¢æ·ç³æ¥æ°æ®</div> |
| | | <!-- 审æ¹å¼¹çª --> |
| | | <el-dialog v-model="auditDialogVisible" |
| | | title="ç³æ¥å®¡æ¹" |
| | | width="760px" |
| | | @close="closeAuditDialog"> |
| | | <el-descriptions :column="2" |
| | | border |
| | | size="small"> |
| | | <el-descriptions-item label="客æ·åç§°">{{ auditForm.customerName }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥è¡¨å¹´åº¦">{{ auditForm.reportYear }}å¹´</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥è¡¨åç§°">{{ auditForm.reportName }}</el-descriptions-item> |
| | | <el-descriptions-item label="æäº¤äºº">{{ auditForm.uploadUserName }}</el-descriptions-item> |
| | | <el-descriptions-item label="æäº¤æ¶é´">{{ auditForm.updateTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="å½åç¶æ">{{ statusText(auditForm.auditStatus) }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="audit-files"> |
| | | <div class="audit-files-title">æ¥è¡¨éä»¶</div> |
| | | <FileUpload v-model="auditForm.reportFiles" |
| | | disabled |
| | | :is-show-tip="false" /> |
| | | </div> |
| | | <el-form :model="auditForm" |
| | | label-width="90px" |
| | | style="margin-top: 12px"> |
| | | <el-form-item label="审æ¹ç»æ"> |
| | | <el-radio-group v-model="auditForm.auditStatus"> |
| | | <el-radio :value="1">éè¿</el-radio> |
| | | <el-radio :value="2">éå</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ¹æè§"> |
| | | <el-input v-model="auditForm.auditRemark" |
| | | type="textarea" |
| | | :rows="3" |
| | | maxlength="500" |
| | | show-word-limit |
| | | placeholder="éåæ¶è¯·å¡«åéååå " /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeAuditDialog">å æ¶</el-button> |
| | | <el-button type="primary" |
| | | :loading="auditSaving" |
| | | @click="submitAudit">ç¡® å®</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, getCurrentInstance, onMounted, reactive, ref } from "vue"; |
| | | import FileUpload from "@/components/FileUpload/index.vue"; |
| | | import { |
| | | auditListCustomerFinancialReport, |
| | | auditCustomerFinancialReport, |
| | | } from "@/api/basicData/customerFinancialReport"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const nowYear = new Date().getFullYear(); |
| | | const yearOptions = [nowYear, nowYear - 1, nowYear - 2]; |
| | | const searchForm = reactive({ customerName: "", reportYear: "", auditStatus: "" }); |
| | | const listData = ref([]); |
| | | const loading = ref(false); |
| | | const total = ref(0); |
| | | const page = reactive({ current: 1, size: 10 }); |
| | | |
| | | const pagedData = computed(() => { |
| | | const start = (page.current - 1) * page.size; |
| | | return listData.value.slice(start, start + page.size); |
| | | }); |
| | | |
| | | const statusText = status => { |
| | | if (status === 1) return "å·²éè¿"; |
| | | if (status === 2) return "å·²éå"; |
| | | return "å¾
审æ¹"; |
| | | }; |
| | | const statusTagType = status => { |
| | | if (status === 1) return "success"; |
| | | if (status === 2) return "danger"; |
| | | return "warning"; |
| | | }; |
| | | |
| | | const load = () => { |
| | | loading.value = true; |
| | | auditListCustomerFinancialReport({ |
| | | customerName: searchForm.customerName, |
| | | reportYear: searchForm.reportYear, |
| | | auditStatus: searchForm.auditStatus, |
| | | }).then(res => { |
| | | listData.value = res.data || []; |
| | | total.value = listData.value.length; |
| | | if (page.current > 1 && pagedData.value.length === 0) { |
| | | page.current = 1; |
| | | } |
| | | }).finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | load(); |
| | | }; |
| | | const resetQuery = () => { |
| | | searchForm.customerName = ""; |
| | | searchForm.reportYear = ""; |
| | | searchForm.auditStatus = ""; |
| | | handleQuery(); |
| | | }; |
| | | const handleSizeChange = size => { |
| | | page.size = size; |
| | | page.current = 1; |
| | | }; |
| | | const handleCurrentChange = current => { |
| | | page.current = current; |
| | | }; |
| | | |
| | | const auditDialogVisible = ref(false); |
| | | const auditSaving = ref(false); |
| | | const auditForm = reactive({ |
| | | id: "", |
| | | customerName: "", |
| | | reportYear: "", |
| | | reportName: "", |
| | | reportFiles: "", |
| | | uploadUserName: "", |
| | | updateTime: "", |
| | | auditStatus: 1, |
| | | auditRemark: "", |
| | | }); |
| | | |
| | | const openAuditDialog = row => { |
| | | Object.keys(auditForm).forEach(key => { |
| | | auditForm[key] = row[key] !== undefined && row[key] !== null ? row[key] : auditForm[key]; |
| | | }); |
| | | auditForm.id = row.id; |
| | | auditForm.customerName = row.customerName; |
| | | auditForm.reportYear = row.reportYear; |
| | | auditForm.reportName = row.reportName; |
| | | auditForm.reportFiles = row.reportFiles; |
| | | auditForm.uploadUserName = row.uploadUserName; |
| | | auditForm.updateTime = row.updateTime; |
| | | auditForm.auditStatus = 1; |
| | | auditForm.auditRemark = ""; |
| | | auditDialogVisible.value = true; |
| | | }; |
| | | |
| | | const closeAuditDialog = () => { |
| | | auditDialogVisible.value = false; |
| | | }; |
| | | |
| | | const submitAudit = () => { |
| | | if (!auditForm.auditStatus) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å®¡æ¹ç»æ"); |
| | | return; |
| | | } |
| | | if (auditForm.auditStatus === 2 && !auditForm.auditRemark) { |
| | | proxy.$modal.msgWarning("éåæ¶è¯·å¡«åéååå "); |
| | | return; |
| | | } |
| | | auditSaving.value = true; |
| | | auditCustomerFinancialReport({ |
| | | id: auditForm.id, |
| | | auditStatus: auditForm.auditStatus, |
| | | auditRemark: auditForm.auditRemark, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹å®æ"); |
| | | auditDialogVisible.value = false; |
| | | load(); |
| | | }).finally(() => { |
| | | auditSaving.value = false; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(load); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search_form { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .search_title { |
| | | color: #606266; |
| | | } |
| | | |
| | | .pagination-wrap { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .no-record { |
| | | color: #909399; |
| | | text-align: center; |
| | | padding: 24px 0; |
| | | } |
| | | |
| | | .audit-files { |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .audit-files-title { |
| | | margin-bottom: 6px; |
| | | color: #606266; |
| | | } |
| | | </style> |
| | |
| | | width="55" |
| | | fixed="left" |
| | | /> |
| | | <el-table-column type="expand" width="60" fixed="left"> |
| | | <el-table-column v-if="false" type="expand" width="60" fixed="left"> |
| | | <template #default="props"> |
| | | <el-table |
| | | :data="props.row.children" |
| | |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å®é
ååéé¢(å
)" |
| | | prop="netContractAmount" |
| | | width="220" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥äºº" |
| | | prop="entryPersonName" |
| | | width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å½å
¥æ¥æ" |
| | | prop="entryDate" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ç¾è®¢æ¥æ" |
| | | prop="executionDate" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="äº¤ä»æ¥æ" |
| | | prop="deliveryDate" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="夿³¨" |
| | | prop="remarks" |
| | | width="200" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column fixed="right" label="æä½" width="320" align="center"> |
| | | <el-table-column label="ä¸å¡å®¡æ¹" width="120" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="businessStatusType(scope.row.businessApprovalStatus)"> |
| | | {{ businessStatusText(scope.row.businessApprovalStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" width="500" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="openForm('view', scope.row)" |
| | | >详æ
|
| | |
| | | <el-button link type="primary" @click="openFileDialog(scope.row)" |
| | | >éä»¶ |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.projectManagementInfoId" |
| | | link |
| | | type="primary" |
| | | @click="openProjectProgress(scope.row)" |
| | | >æ¥ç项ç®è¿åº¦ |
| | | </el-button> |
| | | <el-button |
| | | v-if="![1, 2].includes(scope.row.businessApprovalStatus)" |
| | | v-hasPermi="['business:approval:submit']" |
| | | link |
| | | type="warning" |
| | | :loading="businessActionId === scope.row.id" |
| | | @click="submitBusiness(scope.row)" |
| | | >æäº¤å®¡æ¹ |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.businessApprovalStatus === 1" |
| | | v-hasPermi="['business:approval:review']" |
| | | link |
| | | type="success" |
| | | :loading="businessActionId === scope.row.id" |
| | | @click="reviewBusiness(scope.row, true)" |
| | | >éè¿ |
| | | </el-button> |
| | | <el-button |
| | | v-if="scope.row.businessApprovalStatus === 1" |
| | | v-hasPermi="['business:approval:review']" |
| | | link |
| | | type="danger" |
| | | :loading="businessActionId === scope.row.id" |
| | | @click="reviewBusiness(scope.row, false)" |
| | | >éå |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | v-model="dialogFormVisible" |
| | | :title=" |
| | | operationType === 'add' |
| | | ? 'æ°å¢éå®å°è´¦é¡µé¢' |
| | | ? 'æ°å¢ä¸å¡å°è´¦' |
| | | : operationType === 'edit' |
| | | ? 'ç¼è¾éå®å°è´¦é¡µé¢' |
| | | : 'éå®å°è´¦è¯¦æ
' |
| | | ? 'ç¼è¾ä¸å¡å°è´¦' |
| | | : 'ä¸å¡å°è´¦è¯¦æ
' |
| | | " |
| | | :width="'70%'" |
| | | :operation-type="operationType" |
| | |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | <!-- æ¥ä»·å导å
¥å
¥å£ï¼æ¾å¨è¡¨åé¡¶é¨ï¼éæ©å忾客æ·/ä¸å¡åç --> |
| | | <el-row v-if="operationType === 'add'" style="margin-bottom: 10px"> |
| | | <el-col :span="24" style="text-align: right"> |
| | | <el-button type="primary" plain @click="openQuotationDialog"> |
| | | ä»é宿¥ä»·å¯¼å
¥ |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååç±»åï¼" prop="contractType"> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ååéé¢(å
)ï¼" prop="contractAmount"> |
| | | <el-input-number |
| | | v-model="form.contractAmount" |
| | | :min="0" |
| | | :precision="2" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼" prop="entryPerson"> |
| | | <el-select |
| | | v-model="form.entryPerson" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="primary" |
| | | @click="openProductForm('add')" |
| | | >æ·»å |
| | | </el-button> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | plain |
| | | type="danger" |
| | | @click="deleteProduct" |
| | | >å é¤ |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | | <el-table |
| | | :data="productData" |
| | | border |
| | | @selection-change="productSelected" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | type="selection" |
| | | width="55" |
| | | v-if="operationType !== 'view'" |
| | | :selectable="(row) => !isProductShipped(row)" |
| | | /> |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="产å大类" prop="productCategory" /> |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" /> |
| | | <el-table-column label="æ°é" prop="quantity" /> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" /> |
| | | <el-table-column |
| | | label="å«ç¨åä»·(å
)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="å«ç¨æ»ä»·(å
)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="ä¸å«ç¨æ»ä»·(å
)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column label="æ¯å¦ç产" prop="isProduction" width="150"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isProduction ? 'success' : 'info'"> |
| | | {{ scope.row.isProduction ? "æ¯" : "å¦" }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="60" |
| | | align="center" |
| | | v-if="operationType !== 'view'" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openProductForm('edit', scope.row, scope.$index)" |
| | | >ç¼è¾ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨ï¼" prop="remarks"> |
| | |
| | | createFrameworkOrder, |
| | | listFrameworkOrders, |
| | | frameworkContractShippedQuantity, |
| | | submitBusinessApproval, |
| | | reviewBusinessApproval, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const businessActionId = ref(null); |
| | | const fileList = ref([]); |
| | | const deliveryFileList = ref([]); |
| | | |
| | |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | const businessStatusText = (status) => |
| | | ({ 0: "è稿", 1: "å¾
审æ¹", 2: "å·²éè¿", 3: "å·²éå" }[status ?? 0] || "è稿"); |
| | | const businessStatusType = (status) => |
| | | ({ 0: "info", 1: "warning", 2: "success", 3: "danger" }[status ?? 0] || "info"); |
| | | const submitBusiness = async (row) => { |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `ç¡®å®æäº¤â${row.projectName || row.salesContractNo}âè¿è¡ç®¡ç审æ¹åï¼`, |
| | | "æäº¤ä¸å¡å®¡æ¹", |
| | | { type: "warning", confirmButtonText: "确认æäº¤", cancelButtonText: "åæ¶" } |
| | | ); |
| | | businessActionId.value = row.id; |
| | | await submitBusinessApproval(row.id); |
| | | proxy.$modal.msgSuccess("å·²æäº¤å®¡æ¹"); |
| | | await getList(); |
| | | } catch (error) { |
| | | if (error !== "cancel" && error !== "close") throw error; |
| | | } finally { |
| | | businessActionId.value = null; |
| | | } |
| | | }; |
| | | const reviewBusiness = async (row, approved) => { |
| | | try { |
| | | let remark = ""; |
| | | if (!approved) { |
| | | const result = await ElMessageBox.prompt("请è¾å
¥éååå ", "éåä¸å¡", { |
| | | inputValidator: (value) => Boolean(value?.trim()) || "请è¾å
¥éååå ", |
| | | confirmButtonText: "确认éå", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }); |
| | | remark = result.value.trim(); |
| | | } else { |
| | | await ElMessageBox.confirm( |
| | | "审æ¹éè¿å°èªå¨å建项ç®ï¼æ¯å¦ç»§ç»ï¼", |
| | | "éè¿ä¸å¡å®¡æ¹", |
| | | { type: "success", confirmButtonText: "éè¿å¹¶å»ºé¡¹", cancelButtonText: "åæ¶" } |
| | | ); |
| | | } |
| | | businessActionId.value = row.id; |
| | | await reviewBusinessApproval({ salesLedgerId: row.id, approved, remark }); |
| | | proxy.$modal.msgSuccess(approved ? "审æ¹éè¿ï¼é¡¹ç®å·²èªå¨å»ºç«" : "å·²éå"); |
| | | await getList(); |
| | | } catch (error) { |
| | | if (error !== "cancel" && error !== "close") throw error; |
| | | } finally { |
| | | businessActionId.value = null; |
| | | } |
| | | }; |
| | | // è·å产å大类treeæ°æ® |
| | | const getProductOptions = () => { |
| | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | console.log("productData.value--", productData.value); |
| | | if (productData.value !== null && productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); |
| | | return; |
| | | } |
| | | form.value.productData = proxy.HaveJson(productData.value || []); |
| | | form.value.storageBlobDTOs = fileList; |
| | | form.value.type = 1; |
| | | if (isFrameworkContract.value) { |
| | | form.value.productData = form.value.productData.map((item) => ({ |
| | | ...item, |
| | | quantity: null, |
| | | deliveryDate: null, |
| | | isProduction: false, |
| | | })); |
| | | } |
| | | if (form.value.autoGenerateContractNo) { |
| | | form.value.salesContractNo = ""; |
| | | } |
| | |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | // æ¥ç项ç®è¿åº¦ï¼è·³è½¬é¡¹ç®è¯¦æ
ï¼å¯æ¥çé¶æ®µè¿å±ä¸èµææä»¶ï¼ |
| | | const openProjectProgress = (row) => { |
| | | const projectId = row.projectManagementInfoId || row.projectId; |
| | | if (!projectId) { |
| | | proxy.$modal.msgWarning("该ä¸å¡ææªå
³è项ç®"); |
| | | return; |
| | | } |
| | | router.push(`/projectManagement/Management/detail/${projectId}`); |
| | | }; |
| | | |
| | | // æå¼åè´§å¼¹æ¡ |
| | | const openDeliveryForm = async (row) => { |
| | | // 审æ¹ä¸æ¶æç¤ºç¨æ·å·²æå¾
审æ¹çåè´§è®°å½ |
| | |
| | | const { VITE_APP_ENV } = env; |
| | | const baseUrl = |
| | | env.VITE_APP_ENV === "development" |
| | | ? "http://36.213.90.123:9015" |
| | | ? "http://127.0.0.1:7005" |
| | | : env.VITE_BASE_API; |
| | | const javaUrl = |
| | | env.VITE_APP_ENV === "development" |
| | |
| | | server: { |
| | | port: 80, |
| | | host: true, |
| | | open: true, |
| | | open: false, |
| | | proxy: { |
| | | // https://cn.vitejs.dev/config/#server-proxy |
| | | "/dev-api": { |