2026-07-29 4d15fa8051884869c5b612d0641508874cc71811
feat(erp): 集成SRM供应商服务并优化ERP模块功能

- 在AuthLoginRespVO中添加@JsonFormat注解以确保过期时间格式正确
- 将ERP模块中的供应商服务调用从ErpSupplierService替换为SrmSupplierApi
- 添加采购订单API接口createPurchaseOrder方法用于RPC调用
- 实现采购订单创建的RPC接口逻辑
- 重构ERP财务付款控制器以使用新的供应商API
- 重构ERP采购订单控制器以使用新的供应商API
- 重构ERP采购请求控制器以使用新的供应商API
- 在供应商控制器中集成SrmSupplierApi并实现简单列表查询
- 修改供应商验证逻辑以使用SRM服务
- 在HRM模块中添加用户数据交接相关的错误码常量
- 添加离职申请交接状态更新功能和相关API端点
- 实现离职申请交接状态更新的服务逻辑
- 在MDM模块中添加物料精简列表查询功能
- 实现物料精简列表的数据访问逻辑
已添加158个文件
已修改38个文件
12399 ■■■■■ 文件已修改
.claude/rules/handover-data-query.md 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/SKILL.md 377 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/charts.csv 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/colors.csv 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/icons.csv 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/landing.csv 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/products.csv 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/react-performance.csv 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/astro.csv 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/react.csv 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/stacks/vue.csv 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/styles.csv 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/typography.csv 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/data/web-interface.csv 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/scripts/core.py 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/scripts/design_system.py 1067 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/skills/ui-ux-pro-max/scripts/search.py 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/SRM模块前端联调方案.md 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/unify_supplier_to_srm.sql 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/srm_supplier_apply_select.md 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/模块功能介绍文档.md 513 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_user_handover_init.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateDeserializer.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderCreateReqDTO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/handover/HrmUserHandoverApi.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmHandoverStatusEnum.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/handover/HrmUserHandoverApiImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationHandoverReqVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/handover/HrmUserHandoverDO.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/handover/HrmUserHandoverMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/handover/HrmUserHandoverService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/handover/HrmUserHandoverServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/pendinginspect/MesQcPendingInspectController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourcereceipt/MesWmOutsourceReceiptController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm-api/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm-api/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/SrmSupplierApi.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm-api/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/dto/SrmSupplierRespDTO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/pom.xml 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/SrmSupplierApiImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/SrmSupplierApplyController.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyReviewReqVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/SrmSupplierEvaluationController.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationPageReqVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationRespVO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationSaveReqVO.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/SrmStatisticsController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/vo/SrmTenderStatisticsRespVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierCategoryController.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierContactController.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierContactRespVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierContactSaveReqVO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierPageReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierRespVO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierSaveReqVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardRespVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardSaveReqVO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationRespVO.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationSaveReqVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidOpenRespVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidSaveReqVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialSaveReqVO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidAwardDO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidEvaluationDO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidOpenDO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCategoryDO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCertificateDO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierContactDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierDO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierEvaluationDO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierQuoteDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderBidDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderMaterialDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderProjectDO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidAwardMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidEvaluationMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidOpenMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierApplyMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCategoryMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierContactMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierEvaluationMapper.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierMapper.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierQuoteMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderMaterialMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderProjectMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/AwardStatusEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidStatusEnum.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/CertificateTypeEnum.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/DeliveryStatusEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierApplyStatusEnum.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierTypeEnum.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderTypeEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/framework/web/config/SrmWebConfiguration.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationService.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationServiceImpl.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsServiceImpl.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierCategoryService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierCategoryServiceImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierContactService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierContactServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidService.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_init.sql 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_menu_init.sql 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_test_data.sql 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplier/index.ts 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplierApply/index.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplierEvaluation/index.ts 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/api/srm/tender/index.ts 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplier/index.vue 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplierApply/index.vue 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplierEvaluation/index.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/rules/handover-data-query.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
# æ•°æ®äº¤æŽ¥æŸ¥è¯¢è§„则
## è§„则
所有模块在查询"我的数据"(按用户 ID è¿‡æ»¤çš„业务数据列表)时,**必须调用 `HrmUserHandoverApi.expandUserIds()`** æ‰©å±•用户 ID é›†åˆï¼Œç¡®ä¿äº¤æŽ¥äººèƒ½æŸ¥çœ‹å·²ç¦»èŒäº¤æŽ¥äººçš„业务数据。
## èƒŒæ™¯
当员工离职时,其业务数据(客户、商机、工单、采购申请等)需要交由交接人管理。系统通过 `hrm_user_handover` æ˜ å°„表记录了离职用户与交接目标用户的关联,而非直接修改业务数据的所有者字段。
查询时通过 `expandUserIds` å°†äº¤æŽ¥æºç”¨æˆ· ID çº³å…¥æŸ¥è¯¢èŒƒå›´ï¼Œä½¿äº¤æŽ¥äººèƒ½åŒæ—¶çœ‹åˆ°è‡ªå·±å’Œç¦»èŒäººçš„æ•°æ®ã€‚
## æ ¸å¿ƒ API
```java
// æŽ¥å£ï¼šHrmUserHandoverApi (yudao-module-hrm-api)
public interface HrmUserHandoverApi {
    Set<Long> expandUserIds(Set<Long> userIds);
}
```
## å„模块集成方式
### åŸºæœ¬ç”¨æ³•
```java
// æ³¨å…¥ API
@Resource
private HrmUserHandoverApi handoverApi;
// åœ¨æŸ¥è¯¢å‰æ‰©å±• userId
public PageResult<XxxDO> getXxxPage(XxxPageReqVO reqVO) {
    Long currentUserId = getCurrentUserId();
    Set<Long> expandedUserIds = handoverApi.expandUserIds(Set.of(currentUserId));
    // ç”¨ expandedUserIds åš IN æŸ¥è¯¢
    return mapper.selectPage(reqVO, new LambdaQueryWrapperX<XxxDO>()
            .in(XxxDO::getOwnerUserId, expandedUserIds)
            ...);
}
```
### Mapper å±‚写法
```java
// å¦‚æžœ userId æ˜¯åŠ¨æ€ä¼ å…¥çš„ï¼ˆå¦‚ç®¡ç†å‘˜å¯æŸ¥çœ‹æ‰€æœ‰äººçš„æ•°æ®ï¼‰ï¼Œå…ˆåšåˆ¤æ–­ï¼š
default PageResult<XxxDO> selectPage(XxxPageReqVO reqVO, Set<Long> userIds) {
    return selectPage(reqVO, new LambdaQueryWrapperX<XxxDO>()
            .inIfPresent(XxxDO::getOwnerUserId, CollUtil.isNotEmpty(userIds) ? userIds : null)
            ...);
}
```
## å„模块涉及的查询场景
| æ¨¡å— | æŸ¥è¯¢åœºæ™¯ | æ¶‰åŠè¡¨ | ç”¨æˆ·å­—段 |
|------|----------|--------|----------|
| CRM | æˆ‘的客户 | `crm_customer` | `ownerUserId` |
| CRM | æˆ‘的线索 | `crm_clue` | `ownerUserId` |
| CRM | æˆ‘的联系人 | `crm_contact` | `ownerUserId` |
| CRM | æˆ‘的合同 | `crm_contract` | `ownerUserId` |
| CRM | æˆ‘的商机 | `crm_business` | `ownerUserId` |
| CRM | æˆ‘的回款 | `crm_receivable` | `ownerUserId` |
| CRM | æˆ‘的回款计划 | `crm_receivable_plan` | `ownerUserId` |
| CRM | æˆ‘的报价 | `crm_sale_quotation` | `ownerUserId` |
| ERP | æˆ‘的采购申请 | `erp_purchase_request` | `requestUserId` |
| ERP | æˆ‘的销售订单 | `erp_sale_order` | `saleUserId` |
| ERP | æˆ‘的付款单 | `erp_finance_payment` | `financeUserId` |
| ERP | æˆ‘的收款单 | `erp_finance_receipt` | `financeUserId` |
| MES | æˆ‘的工单/报工/质检等 | å„ MES è¡¨ | `userId` / `inspectorUserId` / `chargeUserId` ç­‰ |
## expandUserIds åŽŸç†
```
hrm_user_handover è¡¨è®°å½•:
┌────┬──────────────┬────────────┬───────────────┬─────────────────┐
│ id â”‚ from_user_id â”‚ to_user_id â”‚ resignation_id│ handover_status â”‚
├────┼──────────────┼────────────┼───────────────┼─────────────────┤
│ 1  â”‚ 100(离职人)  â”‚ 101(交接人)│ 1             â”‚ 20(已完成)       â”‚
└────┴──────────────┴────────────┴───────────────┴─────────────────┘
expandUserIds({101}) â†’ {101, 100}
→ äº¤æŽ¥äºº101查询时:WHERE owner_user_id IN (101, 100)
→ åŒæ—¶çœ‹åˆ°è‡ªå·±çš„和离职人的数据
```
## æ³¨æ„äº‹é¡¹
- `expandUserIds` åªæ‰©å±•**已完成的交接记录**(`handoverStatus = 20`)
- ä¼ å…¥ç©ºé›†åˆè¿”回空集合
- æŸ¥è¯¢æ€§èƒ½ï¼šæ˜ å°„表数据量极小(仅为已离职人员数),可放心在每次查询前调用
- å¦‚果查询要求严格只看本人数据(如个人设置页),则不应调用 `expandUserIds`
## Maven ä¾èµ–
需要在模块的 `pom.xml` ä¸­æ·»åР坹 HRM API çš„依赖:
```xml
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>yudao-module-hrm-api</artifactId>
    <version>${revision}</version>
</dependency>
```
.claude/skills/ui-ux-pro-max/SKILL.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,377 @@
---
name: ui-ux-pro-max
description: "UI/UX design intelligence â€” AUTO-TRIGGER on any frontend/design conversation. Whenever user mentions: ç•Œé¢ å‰ç«¯ é¡µé¢ æ ·å¼ CSS Tailwind component ç»„ä»¶ è®¾è®¡ dashboard åŽå° admin ç®¡ç†ç³»ç»Ÿ landing è½åœ°é¡µ website ç½‘ç«™ UI UX é…è‰² å­—体 å¸ƒå±€ æŒ‰é’® è¡¨å• è¡¨æ ¼ å›¾è¡¨ å¯¼èˆª ä¾§è¾¹æ  å¼¹çª— å¡ç‰‡ å“åº”式 æš—黑模式 åŠ¨æ•ˆ äº¤äº’ ç¾ŽåŒ– å¥½çœ‹ä¸€ç‚¹ ä¼˜åŒ–样式 æ”¹é¢œè‰² æ”¹å¸ƒå±€ å‰ç«¯å¼€å‘ Vue React HTML div å¼¹æ€§å¸ƒå±€ grid flexbox。Database: 67 styles, 96 palettes, 57 font pairings, 25 charts, 13 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design."
---
# UI/UX Pro Max - Design Intelligence
Comprehensive design guide for web and mobile applications. Contains 67 styles, 96 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 13 technology stacks. Searchable database with priority-based recommendations.
## When to Apply
Reference these guidelines when:
- Designing new UI components or pages
- Choosing color palettes and typography
- Reviewing code for UX issues
- Building landing pages or dashboards
- Implementing accessibility requirements
## Rule Categories by Priority
| Priority | Category | Impact | Domain |
|----------|----------|--------|--------|
| 1 | Accessibility | CRITICAL | `ux` |
| 2 | Touch & Interaction | CRITICAL | `ux` |
| 3 | Performance | HIGH | `ux` |
| 4 | Layout & Responsive | HIGH | `ux` |
| 5 | Typography & Color | MEDIUM | `typography`, `color` |
| 6 | Animation | MEDIUM | `ux` |
| 7 | Style Selection | MEDIUM | `style`, `product` |
| 8 | Charts & Data | LOW | `chart` |
## Quick Reference
### 1. Accessibility (CRITICAL)
- `color-contrast` - Minimum 4.5:1 ratio for normal text
- `focus-states` - Visible focus rings on interactive elements
- `alt-text` - Descriptive alt text for meaningful images
- `aria-labels` - aria-label for icon-only buttons
- `keyboard-nav` - Tab order matches visual order
- `form-labels` - Use label with for attribute
### 2. Touch & Interaction (CRITICAL)
- `touch-target-size` - Minimum 44x44px touch targets
- `hover-vs-tap` - Use click/tap for primary interactions
- `loading-buttons` - Disable button during async operations
- `error-feedback` - Clear error messages near problem
- `cursor-pointer` - Add cursor-pointer to clickable elements
### 3. Performance (HIGH)
- `image-optimization` - Use WebP, srcset, lazy loading
- `reduced-motion` - Check prefers-reduced-motion
- `content-jumping` - Reserve space for async content
### 4. Layout & Responsive (HIGH)
- `viewport-meta` - width=device-width initial-scale=1
- `readable-font-size` - Minimum 16px body text on mobile
- `horizontal-scroll` - Ensure content fits viewport width
- `z-index-management` - Define z-index scale (10, 20, 30, 50)
### 5. Typography & Color (MEDIUM)
- `line-height` - Use 1.5-1.75 for body text
- `line-length` - Limit to 65-75 characters per line
- `font-pairing` - Match heading/body font personalities
### 6. Animation (MEDIUM)
- `duration-timing` - Use 150-300ms for micro-interactions
- `transform-performance` - Use transform/opacity, not width/height
- `loading-states` - Skeleton screens or spinners
### 7. Style Selection (MEDIUM)
- `style-match` - Match style to product type
- `consistency` - Use same style across all pages
- `no-emoji-icons` - Use SVG icons, not emojis
### 8. Charts & Data (LOW)
- `chart-type` - Match chart type to data type
- `color-guidance` - Use accessible color palettes
- `data-table` - Provide table alternative for accessibility
## How to Use
Search specific domains using the CLI tool below.
---
## Prerequisites
Check if Python is installed:
```bash
python3 --version || python --version
```
If Python is not installed, install it based on user's OS:
**macOS:**
```bash
brew install python3
```
**Ubuntu/Debian:**
```bash
sudo apt update && sudo apt install python3
```
**Windows:**
```powershell
winget install Python.Python.3.12
```
---
## How to Use This Skill
When user requests UI/UX work (design, build, create, implement, review, fix, improve), follow this workflow:
### Step 1: Analyze User Requirements
Extract key information from user request:
- **Product type**: SaaS, e-commerce, portfolio, dashboard, landing page, etc.
- **Style keywords**: minimal, playful, professional, elegant, dark mode, etc.
- **Industry**: healthcare, fintech, gaming, education, etc.
- **Stack**: React, Vue, Next.js, or default to `html-tailwind`
### Step 2: Generate Design System (REQUIRED)
**Always start with `--design-system`** to get comprehensive recommendations with reasoning:
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
```
This command:
1. Searches 5 domains in parallel (product, style, color, landing, typography)
2. Applies reasoning rules from `ui-reasoning.csv` to select best matches
3. Returns complete design system: pattern, style, colors, typography, effects
4. Includes anti-patterns to avoid
**Example:**
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa"
```
### Step 2b: Persist Design System (Master + Overrides Pattern)
To save the design system for hierarchical retrieval across sessions, add `--persist`:
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name"
```
This creates:
- `design-system/MASTER.md` â€” Global Source of Truth with all design rules
- `design-system/pages/` â€” Folder for page-specific overrides
**With page-specific override:**
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name" --page "dashboard"
```
This also creates:
- `design-system/pages/dashboard.md` â€” Page-specific deviations from Master
**How hierarchical retrieval works:**
1. When building a specific page (e.g., "Checkout"), first check `design-system/pages/checkout.md`
2. If the page file exists, its rules **override** the Master file
3. If not, use `design-system/MASTER.md` exclusively
### Step 3: Supplement with Detailed Searches (as needed)
After getting the design system, use domain searches to get additional details:
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n <max_results>]
```
**When to use detailed searches:**
| Need | Domain | Example |
|------|--------|---------|
| More style options | `style` | `--domain style "glassmorphism dark"` |
| Chart recommendations | `chart` | `--domain chart "real-time dashboard"` |
| UX best practices | `ux` | `--domain ux "animation accessibility"` |
| Alternative fonts | `typography` | `--domain typography "elegant luxury"` |
| Landing structure | `landing` | `--domain landing "hero social-proof"` |
### Step 4: Stack Guidelines (Default: html-tailwind)
Get implementation-specific best practices. If user doesn't specify a stack, **default to `html-tailwind`**.
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack html-tailwind
```
Available stacks: `html-tailwind`, `react`, `nextjs`, `vue`, `svelte`, `swiftui`, `react-native`, `flutter`, `shadcn`, `jetpack-compose`
---
## Search Reference
### Available Domains
| Domain | Use For | Example Keywords |
|--------|---------|------------------|
| `product` | Product type recommendations | SaaS, e-commerce, portfolio, healthcare, beauty, service |
| `style` | UI styles, colors, effects | glassmorphism, minimalism, dark mode, brutalism |
| `typography` | Font pairings, Google Fonts | elegant, playful, professional, modern |
| `color` | Color palettes by product type | saas, ecommerce, healthcare, beauty, fintech, service |
| `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof |
| `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie |
| `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading |
| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache |
| `web` | Web interface guidelines | aria, focus, keyboard, semantic, virtualize |
| `prompt` | AI prompts, CSS keywords | (style name) |
### Available Stacks
| Stack | Focus |
|-------|-------|
| `html-tailwind` | Tailwind utilities, responsive, a11y (DEFAULT) |
| `react` | State, hooks, performance, patterns |
| `nextjs` | SSR, routing, images, API routes |
| `vue` | Composition API, Pinia, Vue Router |
| `svelte` | Runes, stores, SvelteKit |
| `swiftui` | Views, State, Navigation, Animation |
| `react-native` | Components, Navigation, Lists |
| `flutter` | Widgets, State, Layout, Theming |
| `shadcn` | shadcn/ui components, theming, forms, patterns |
| `jetpack-compose` | Composables, Modifiers, State Hoisting, Recomposition |
---
## Example Workflow
**User request:** "Làm landing page cho dịch vụ chăm sóc da chuyên nghiệp"
### Step 1: Analyze Requirements
- Product type: Beauty/Spa service
- Style keywords: elegant, professional, soft
- Industry: Beauty/Wellness
- Stack: html-tailwind (default)
### Step 2: Generate Design System (REQUIRED)
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service elegant" --design-system -p "Serenity Spa"
```
**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns.
### Step 3: Supplement with Detailed Searches (as needed)
```bash
# Get UX guidelines for animation and accessibility
python3 skills/ui-ux-pro-max/scripts/search.py "animation accessibility" --domain ux
# Get alternative typography options if needed
python3 skills/ui-ux-pro-max/scripts/search.py "elegant luxury serif" --domain typography
```
### Step 4: Stack Guidelines
```bash
python3 skills/ui-ux-pro-max/scripts/search.py "layout responsive form" --stack html-tailwind
```
**Then:** Synthesize design system + detailed searches and implement the design.
---
## Output Formats
The `--design-system` flag supports two output formats:
```bash
# ASCII box (default) - best for terminal display
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
# Markdown - best for documentation
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown
```
---
## Tips for Better Results
1. **Be specific with keywords** - "healthcare SaaS dashboard" > "app"
2. **Search multiple times** - Different keywords reveal different insights
3. **Combine domains** - Style + Typography + Color = Complete design system
4. **Always check UX** - Search "animation", "z-index", "accessibility" for common issues
5. **Use stack flag** - Get implementation-specific best practices
6. **Iterate** - If first search doesn't match, try different keywords
---
## Common Rules for Professional UI
These are frequently overlooked issues that make UI look unprofessional:
### Icons & Visual Elements
| Rule | Do | Don't |
|------|----|----- |
| **No emoji icons** | Use SVG icons (Heroicons, Lucide, Simple Icons) | Use emojis like ðŸŽ¨ ðŸš€ âš™ï¸ as UI icons |
| **Stable hover states** | Use color/opacity transitions on hover | Use scale transforms that shift layout |
| **Correct brand logos** | Research official SVG from Simple Icons | Guess or use incorrect logo paths |
| **Consistent icon sizing** | Use fixed viewBox (24x24) with w-6 h-6 | Mix different icon sizes randomly |
### Interaction & Cursor
| Rule | Do | Don't |
|------|----|----- |
| **Cursor pointer** | Add `cursor-pointer` to all clickable/hoverable cards | Leave default cursor on interactive elements |
| **Hover feedback** | Provide visual feedback (color, shadow, border) | No indication element is interactive |
| **Smooth transitions** | Use `transition-colors duration-200` | Instant state changes or too slow (>500ms) |
### Light/Dark Mode Contrast
| Rule | Do | Don't |
|------|----|----- |
| **Glass card light mode** | Use `bg-white/80` or higher opacity | Use `bg-white/10` (too transparent) |
| **Text contrast light** | Use `#0F172A` (slate-900) for text | Use `#94A3B8` (slate-400) for body text |
| **Muted text light** | Use `#475569` (slate-600) minimum | Use gray-400 or lighter |
| **Border visibility** | Use `border-gray-200` in light mode | Use `border-white/10` (invisible) |
### Layout & Spacing
| Rule | Do | Don't |
|------|----|----- |
| **Floating navbar** | Add `top-4 left-4 right-4` spacing | Stick navbar to `top-0 left-0 right-0` |
| **Content padding** | Account for fixed navbar height | Let content hide behind fixed elements |
| **Consistent max-width** | Use same `max-w-6xl` or `max-w-7xl` | Mix different container widths |
---
## Pre-Delivery Checklist
Before delivering UI code, verify these items:
### Visual Quality
- [ ] No emojis used as icons (use SVG instead)
- [ ] All icons from consistent icon set (Heroicons/Lucide)
- [ ] Brand logos are correct (verified from Simple Icons)
- [ ] Hover states don't cause layout shift
- [ ] Use theme colors directly (bg-primary) not var() wrapper
### Interaction
- [ ] All clickable elements have `cursor-pointer`
- [ ] Hover states provide clear visual feedback
- [ ] Transitions are smooth (150-300ms)
- [ ] Focus states visible for keyboard navigation
### Light/Dark Mode
- [ ] Light mode text has sufficient contrast (4.5:1 minimum)
- [ ] Glass/transparent elements visible in light mode
- [ ] Borders visible in both modes
- [ ] Test both modes before delivery
### Layout
- [ ] Floating elements have proper spacing from edges
- [ ] No content hidden behind fixed navbars
- [ ] Responsive at 375px, 768px, 1024px, 1440px
- [ ] No horizontal scroll on mobile
### Accessibility
- [ ] All images have alt text
- [ ] Form inputs have labels
- [ ] Color is not the only indicator
- [ ] `prefers-reduced-motion` respected
.claude/skills/ui-ux-pro-max/data/charts.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
No,Data Type,Keywords,Best Chart Type,Secondary Options,Color Guidance,Performance Impact,Accessibility Notes,Library Recommendation,Interactive Level
1,Trend Over Time,"trend, time-series, line, growth, timeline, progress",Line Chart,"Area Chart, Smooth Area",Primary: #0080FF. Multiple series: use distinct colors. Fill: 20% opacity,⚡ Excellent (optimized),✓ Clear line patterns for colorblind users. Add pattern overlays.,"Chart.js, Recharts, ApexCharts",Hover + Zoom
2,Compare Categories,"compare, categories, bar, comparison, ranking",Bar Chart (Horizontal or Vertical),"Column Chart, Grouped Bar",Each bar: distinct color. Category: grouped same color. Sorted: descending order,⚡ Excellent,✓ Easy to compare. Add value labels on bars for clarity.,"Chart.js, Recharts, D3.js",Hover + Sort
3,Part-to-Whole,"part-to-whole, pie, donut, percentage, proportion, share",Pie Chart or Donut,"Stacked Bar, Treemap",Colors: 5-6 max. Contrasting palette. Large slices first. Use labels.,⚡ Good (limit 6 slices),⚠ Hard for accessibility. Better: Stacked bar with legend. Avoid pie if >5 items.,"Chart.js, Recharts, D3.js",Hover + Drill
4,Correlation/Distribution,"correlation, distribution, scatter, relationship, pattern",Scatter Plot or Bubble Chart,"Heat Map, Matrix",Color axis: gradient (blue-red). Size: relative. Opacity: 0.6-0.8 to show density,⚠ Moderate (many points),⚠ Provide data table alternative. Use pattern + color distinction.,"D3.js, Plotly, Recharts",Hover + Brush
5,Heatmap/Intensity,"heatmap, heat-map, intensity, density, matrix",Heat Map or Choropleth,"Grid Heat Map, Bubble Heat",Gradient: Cool (blue) to Hot (red). Scale: clear legend. Divergent for Â±data,⚡ Excellent (color CSS),⚠ Colorblind: Use pattern overlay. Provide numerical legend.,"D3.js, Plotly, ApexCharts",Hover + Zoom
6,Geographic Data,"geographic, map, location, region, geo, spatial","Choropleth Map, Bubble Map",Geographic Heat Map,Regional: single color gradient or categorized colors. Legend: clear scale,⚠ Moderate (rendering),⚠ Include text labels for regions. Provide data table alternative.,"D3.js, Mapbox, Leaflet",Pan + Zoom + Drill
7,Funnel/Flow,funnel/flow,"Funnel Chart, Sankey",Waterfall (for flows),Stages: gradient (starting color â†’ ending color). Show conversion %,⚡ Good,✓ Clear stage labels + percentages. Good for accessibility if labeled.,"D3.js, Recharts, Custom SVG",Hover + Drill
8,Performance vs Target,performance-vs-target,Gauge Chart or Bullet Chart,"Dial, Thermometer",Performance: Red→Yellow→Green gradient. Target: marker line. Threshold colors,⚡ Good,✓ Add numerical value + percentage label beside gauge.,"D3.js, ApexCharts, Custom SVG",Hover
9,Time-Series Forecast,time-series-forecast,Line with Confidence Band,Ribbon Chart,Actual: solid line #0080FF. Forecast: dashed #FF9500. Band: light shading,⚡ Good,✓ Clearly distinguish actual vs forecast. Add legend.,"Chart.js, ApexCharts, Plotly",Hover + Toggle
10,Anomaly Detection,anomaly-detection,Line Chart with Highlights,Scatter with Alert,Normal: blue #0080FF. Anomaly: red #FF0000 circle/square marker + alert,⚡ Good,✓ Circle/marker for anomalies. Add text alert annotation.,"D3.js, Plotly, ApexCharts",Hover + Alert
11,Hierarchical/Nested Data,hierarchical/nested-data,Treemap,"Sunburst, Nested Donut, Icicle",Parent: distinct hues. Children: lighter shades. White borders 2-3px.,⚠ Moderate,⚠ Poor - provide table alternative. Label large areas.,"D3.js, Recharts, ApexCharts",Hover + Drilldown
12,Flow/Process Data,flow/process-data,Sankey Diagram,"Alluvial, Chord Diagram",Gradient from source to target. Opacity 0.4-0.6 for flows.,⚠ Moderate,⚠ Poor - provide flow table alternative.,"D3.js (d3-sankey), Plotly",Hover + Drilldown
13,Cumulative Changes,cumulative-changes,Waterfall Chart,"Stacked Bar, Cascade",Increases: #4CAF50. Decreases: #F44336. Start: #2196F3. End: #0D47A1.,⚡ Good,✓ Good - clear directional colors with labels.,"ApexCharts, Highcharts, Plotly",Hover
14,Multi-Variable Comparison,multi-variable-comparison,Radar/Spider Chart,"Parallel Coordinates, Grouped Bar",Single: #0080FF 20% fill. Multiple: distinct colors per dataset.,⚡ Good,⚠ Moderate - limit 5-8 axes. Add data table.,"Chart.js, Recharts, ApexCharts",Hover + Toggle
15,Stock/Trading OHLC,stock/trading-ohlc,Candlestick Chart,"OHLC Bar, Heikin-Ashi",Bullish: #26A69A. Bearish: #EF5350. Volume: 40% opacity below.,⚡ Good,⚠ Moderate - provide OHLC data table.,"Lightweight Charts (TradingView), ApexCharts",Real-time + Hover + Zoom
16,Relationship/Connection Data,relationship/connection-data,Network Graph,"Hierarchical Tree, Adjacency Matrix",Node types: categorical colors. Edges: #90A4AE 60% opacity.,❌ Poor (500+ nodes struggles),❌ Very Poor - provide adjacency list alternative.,"D3.js (d3-force), Vis.js, Cytoscape.js",Drilldown + Hover + Drag
17,Distribution/Statistical,distribution/statistical,Box Plot,"Violin Plot, Beeswarm",Box: #BBDEFB. Border: #1976D2. Median: #D32F2F. Outliers: #F44336.,⚡ Excellent,"✓ Good - include stats table (min, Q1, median, Q3, max).","Plotly, D3.js, Chart.js (plugin)",Hover
18,Performance vs Target (Compact),performance-vs-target-(compact),Bullet Chart,"Gauge, Progress Bar","Ranges: #FFCDD2, #FFF9C4, #C8E6C9. Performance: #1976D2. Target: black 3px.",⚡ Excellent,✓ Excellent - compact with clear values.,"D3.js, Plotly, Custom SVG",Hover
19,Proportional/Percentage,proportional/percentage,Waffle Chart,"Pictogram, Stacked Bar 100%",10x10 grid. 3-5 categories max. 2-3px spacing between squares.,⚡ Good,✓ Good - better than pie for accessibility.,"D3.js, React-Waffle, Custom CSS Grid",Hover
20,Hierarchical Proportional,hierarchical-proportional,Sunburst Chart,"Treemap, Icicle, Circle Packing",Center to outer: darker to lighter. 15-20% lighter per level.,⚠ Moderate,⚠ Poor - provide hierarchy table alternative.,"D3.js (d3-hierarchy), Recharts, ApexCharts",Drilldown + Hover
21,Root Cause Analysis,"root cause, decomposition, tree, hierarchy, drill-down, ai-split",Decomposition Tree,"Decision Tree, Flow Chart",Nodes: #2563EB (Primary) vs #EF4444 (Negative impact). Connectors: Neutral grey.,⚠ Moderate (calculation heavy),✓ clear hierarchy. Allow keyboard navigation for nodes.,"Power BI (native), React-Flow, Custom D3.js",Drill + Expand
22,3D Spatial Data,"3d, spatial, immersive, terrain, molecular, volumetric",3D Scatter/Surface Plot,"Volumetric Rendering, Point Cloud",Depth cues: lighting/shading. Z-axis: color gradient (cool to warm).,❌ Heavy (WebGL required),❌ Poor - requires alternative 2D view or data table.,"Three.js, Deck.gl, Plotly 3D",Rotate + Zoom + VR
23,Real-Time Streaming,"streaming, real-time, ticker, live, velocity, pulse",Streaming Area Chart,"Ticker Tape, Moving Gauge",Current: Bright Pulse (#00FF00). History: Fading opacity. Grid: Dark.,⚡ Optimized (canvas/webgl),⚠ Flashing elements - provide pause button. High contrast.,Smoothed D3.js, CanvasJS
24,Sentiment/Emotion,"sentiment, emotion, nlp, opinion, feeling",Word Cloud with Sentiment,"Sentiment Arc, Radar Chart",Positive: #22C55E. Negative: #EF4444. Neutral: #94A3B8. Size = Frequency.,⚡ Good,⚠ Word clouds poor for screen readers. Use list view.,"D3-cloud, Highcharts, Nivo",Hover + Filter
25,Process Mining,"process, mining, variants, path, bottleneck, log",Process Map / Graph,"Directed Acyclic Graph (DAG), Petri Net",Happy path: #10B981 (Thick). Deviations: #F59E0B (Thin). Bottlenecks: #EF4444.,⚠ Moderate to Heavy,⚠ Complex graphs hard to navigate. Provide path summary.,"React-Flow, Cytoscape.js, Recharts",Drag + Node-Click
.claude/skills/ui-ux-pro-max/data/colors.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
No,Product Type,Primary (Hex),Secondary (Hex),CTA (Hex),Background (Hex),Text (Hex),Border (Hex),Notes
1,SaaS (General),#2563EB,#3B82F6,#F97316,#F8FAFC,#1E293B,#E2E8F0,Trust blue + orange CTA contrast
2,Micro SaaS,#6366F1,#818CF8,#10B981,#F5F3FF,#1E1B4B,#E0E7FF,Indigo primary + emerald CTA
3,E-commerce,#059669,#10B981,#F97316,#ECFDF5,#064E3B,#A7F3D0,Success green + urgency orange
4,E-commerce Luxury,#1C1917,#44403C,#CA8A04,#FAFAF9,#0C0A09,#D6D3D1,Premium dark + gold accent
5,Service Landing Page,#0EA5E9,#38BDF8,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Sky blue trust + warm CTA
6,B2B Service,#0F172A,#334155,#0369A1,#F8FAFC,#020617,#E2E8F0,Professional navy + blue CTA
7,Financial Dashboard,#0F172A,#1E293B,#22C55E,#020617,#F8FAFC,#334155,Dark bg + green positive indicators
8,Analytics Dashboard,#1E40AF,#3B82F6,#F59E0B,#F8FAFC,#1E3A8A,#DBEAFE,Blue data + amber highlights
9,Healthcare App,#0891B2,#22D3EE,#059669,#ECFEFF,#164E63,#A5F3FC,Calm cyan + health green
10,Educational App,#4F46E5,#818CF8,#F97316,#EEF2FF,#1E1B4B,#C7D2FE,Playful indigo + energetic orange
11,Creative Agency,#EC4899,#F472B6,#06B6D4,#FDF2F8,#831843,#FBCFE8,Bold pink + cyan accent
12,Portfolio/Personal,#18181B,#3F3F46,#2563EB,#FAFAFA,#09090B,#E4E4E7,Monochrome + blue accent
13,Gaming,#7C3AED,#A78BFA,#F43F5E,#0F0F23,#E2E8F0,#4C1D95,Neon purple + rose action
14,Government/Public Service,#0F172A,#334155,#0369A1,#F8FAFC,#020617,#E2E8F0,High contrast navy + blue
15,Fintech/Crypto,#F59E0B,#FBBF24,#8B5CF6,#0F172A,#F8FAFC,#334155,Gold trust + purple tech
16,Social Media App,#E11D48,#FB7185,#2563EB,#FFF1F2,#881337,#FECDD3,Vibrant rose + engagement blue
17,Productivity Tool,#0D9488,#14B8A6,#F97316,#F0FDFA,#134E4A,#99F6E4,Teal focus + action orange
18,Design System/Component Library,#4F46E5,#6366F1,#F97316,#EEF2FF,#312E81,#C7D2FE,Indigo brand + doc hierarchy
19,AI/Chatbot Platform,#7C3AED,#A78BFA,#06B6D4,#FAF5FF,#1E1B4B,#DDD6FE,AI purple + cyan interactions
20,NFT/Web3 Platform,#8B5CF6,#A78BFA,#FBBF24,#0F0F23,#F8FAFC,#4C1D95,Purple tech + gold value
21,Creator Economy Platform,#EC4899,#F472B6,#F97316,#FDF2F8,#831843,#FBCFE8,Creator pink + engagement orange
22,Sustainability/ESG Platform,#059669,#10B981,#0891B2,#ECFDF5,#064E3B,#A7F3D0,Nature green + ocean blue
23,Remote Work/Collaboration Tool,#6366F1,#818CF8,#10B981,#F5F3FF,#312E81,#E0E7FF,Calm indigo + success green
24,Mental Health App,#8B5CF6,#C4B5FD,#10B981,#FAF5FF,#4C1D95,#EDE9FE,Calming lavender + wellness green
25,Pet Tech App,#F97316,#FB923C,#2563EB,#FFF7ED,#9A3412,#FED7AA,Playful orange + trust blue
26,Smart Home/IoT Dashboard,#1E293B,#334155,#22C55E,#0F172A,#F8FAFC,#475569,Dark tech + status green
27,EV/Charging Ecosystem,#0891B2,#22D3EE,#22C55E,#ECFEFF,#164E63,#A5F3FC,Electric cyan + eco green
28,Subscription Box Service,#D946EF,#E879F9,#F97316,#FDF4FF,#86198F,#F5D0FE,Excitement purple + urgency orange
29,Podcast Platform,#1E1B4B,#312E81,#F97316,#0F0F23,#F8FAFC,#4338CA,Dark audio + warm accent
30,Dating App,#E11D48,#FB7185,#F97316,#FFF1F2,#881337,#FECDD3,Romantic rose + warm orange
31,Micro-Credentials/Badges Platform,#0369A1,#0EA5E9,#CA8A04,#F0F9FF,#0C4A6E,#BAE6FD,Trust blue + achievement gold
32,Knowledge Base/Documentation,#475569,#64748B,#2563EB,#F8FAFC,#1E293B,#E2E8F0,Neutral grey + link blue
33,Hyperlocal Services,#059669,#10B981,#F97316,#ECFDF5,#064E3B,#A7F3D0,Location green + action orange
34,Beauty/Spa/Wellness Service,#EC4899,#F9A8D4,#8B5CF6,#FDF2F8,#831843,#FBCFE8,Soft pink + lavender luxury
35,Luxury/Premium Brand,#1C1917,#44403C,#CA8A04,#FAFAF9,#0C0A09,#D6D3D1,Premium black + gold accent
36,Restaurant/Food Service,#DC2626,#F87171,#CA8A04,#FEF2F2,#450A0A,#FECACA,Appetizing red + warm gold
37,Fitness/Gym App,#F97316,#FB923C,#22C55E,#1F2937,#F8FAFC,#374151,Energy orange + success green
38,Real Estate/Property,#0F766E,#14B8A6,#0369A1,#F0FDFA,#134E4A,#99F6E4,Trust teal + professional blue
39,Travel/Tourism Agency,#0EA5E9,#38BDF8,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Sky blue + adventure orange
40,Hotel/Hospitality,#1E3A8A,#3B82F6,#CA8A04,#F8FAFC,#1E40AF,#BFDBFE,Luxury navy + gold service
41,Wedding/Event Planning,#DB2777,#F472B6,#CA8A04,#FDF2F8,#831843,#FBCFE8,Romantic pink + elegant gold
42,Legal Services,#1E3A8A,#1E40AF,#B45309,#F8FAFC,#0F172A,#CBD5E1,Authority navy + trust gold
43,Insurance Platform,#0369A1,#0EA5E9,#22C55E,#F0F9FF,#0C4A6E,#BAE6FD,Security blue + protected green
44,Banking/Traditional Finance,#0F172A,#1E3A8A,#CA8A04,#F8FAFC,#020617,#E2E8F0,Trust navy + premium gold
45,Online Course/E-learning,#0D9488,#2DD4BF,#F97316,#F0FDFA,#134E4A,#5EEAD4,Progress teal + achievement orange
46,Non-profit/Charity,#0891B2,#22D3EE,#F97316,#ECFEFF,#164E63,#A5F3FC,Compassion blue + action orange
47,Music Streaming,#1E1B4B,#4338CA,#22C55E,#0F0F23,#F8FAFC,#312E81,Dark audio + play green
48,Video Streaming/OTT,#0F0F23,#1E1B4B,#E11D48,#000000,#F8FAFC,#312E81,Cinema dark + play red
49,Job Board/Recruitment,#0369A1,#0EA5E9,#22C55E,#F0F9FF,#0C4A6E,#BAE6FD,Professional blue + success green
50,Marketplace (P2P),#7C3AED,#A78BFA,#22C55E,#FAF5FF,#4C1D95,#DDD6FE,Trust purple + transaction green
51,Logistics/Delivery,#2563EB,#3B82F6,#F97316,#EFF6FF,#1E40AF,#BFDBFE,Tracking blue + delivery orange
52,Agriculture/Farm Tech,#15803D,#22C55E,#CA8A04,#F0FDF4,#14532D,#BBF7D0,Earth green + harvest gold
53,Construction/Architecture,#64748B,#94A3B8,#F97316,#F8FAFC,#334155,#E2E8F0,Industrial grey + safety orange
54,Automotive/Car Dealership,#1E293B,#334155,#DC2626,#F8FAFC,#0F172A,#E2E8F0,Premium dark + action red
55,Photography Studio,#18181B,#27272A,#F8FAFC,#000000,#FAFAFA,#3F3F46,Pure black + white contrast
56,Coworking Space,#F59E0B,#FBBF24,#2563EB,#FFFBEB,#78350F,#FDE68A,Energetic amber + booking blue
57,Cleaning Service,#0891B2,#22D3EE,#22C55E,#ECFEFF,#164E63,#A5F3FC,Fresh cyan + clean green
58,Home Services (Plumber/Electrician),#1E40AF,#3B82F6,#F97316,#EFF6FF,#1E3A8A,#BFDBFE,Professional blue + urgent orange
59,Childcare/Daycare,#F472B6,#FBCFE8,#22C55E,#FDF2F8,#9D174D,#FCE7F3,Soft pink + safe green
60,Senior Care/Elderly,#0369A1,#38BDF8,#22C55E,#F0F9FF,#0C4A6E,#E0F2FE,Calm blue + reassuring green
61,Medical Clinic,#0891B2,#22D3EE,#22C55E,#F0FDFA,#134E4A,#CCFBF1,Medical teal + health green
62,Pharmacy/Drug Store,#15803D,#22C55E,#0369A1,#F0FDF4,#14532D,#BBF7D0,Pharmacy green + trust blue
63,Dental Practice,#0EA5E9,#38BDF8,#FBBF24,#F0F9FF,#0C4A6E,#BAE6FD,Fresh blue + smile yellow
64,Veterinary Clinic,#0D9488,#14B8A6,#F97316,#F0FDFA,#134E4A,#99F6E4,Caring teal + warm orange
65,Florist/Plant Shop,#15803D,#22C55E,#EC4899,#F0FDF4,#14532D,#BBF7D0,Natural green + floral pink
66,Bakery/Cafe,#92400E,#B45309,#F8FAFC,#FEF3C7,#78350F,#FDE68A,Warm brown + cream white
67,Coffee Shop,#78350F,#92400E,#FBBF24,#FEF3C7,#451A03,#FDE68A,Coffee brown + warm gold
68,Brewery/Winery,#7C2D12,#B91C1C,#CA8A04,#FEF2F2,#450A0A,#FECACA,Deep burgundy + craft gold
69,Airline,#1E3A8A,#3B82F6,#F97316,#EFF6FF,#1E40AF,#BFDBFE,Sky blue + booking orange
70,News/Media Platform,#DC2626,#EF4444,#1E40AF,#FEF2F2,#450A0A,#FECACA,Breaking red + link blue
71,Magazine/Blog,#18181B,#3F3F46,#EC4899,#FAFAFA,#09090B,#E4E4E7,Editorial black + accent pink
72,Freelancer Platform,#6366F1,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Creative indigo + hire green
73,Consulting Firm,#0F172A,#334155,#CA8A04,#F8FAFC,#020617,#E2E8F0,Authority navy + premium gold
74,Marketing Agency,#EC4899,#F472B6,#06B6D4,#FDF2F8,#831843,#FBCFE8,Bold pink + creative cyan
75,Event Management,#7C3AED,#A78BFA,#F97316,#FAF5FF,#4C1D95,#DDD6FE,Excitement purple + action orange
76,Conference/Webinar Platform,#1E40AF,#3B82F6,#22C55E,#EFF6FF,#1E3A8A,#BFDBFE,Professional blue + join green
77,Membership/Community,#7C3AED,#A78BFA,#22C55E,#FAF5FF,#4C1D95,#DDD6FE,Community purple + join green
78,Newsletter Platform,#0369A1,#0EA5E9,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Trust blue + subscribe orange
79,Digital Products/Downloads,#6366F1,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Digital indigo + buy green
80,Church/Religious Organization,#7C3AED,#A78BFA,#CA8A04,#FAF5FF,#4C1D95,#DDD6FE,Spiritual purple + warm gold
81,Sports Team/Club,#DC2626,#EF4444,#FBBF24,#FEF2F2,#7F1D1D,#FECACA,Team red + championship gold
82,Museum/Gallery,#18181B,#27272A,#F8FAFC,#FAFAFA,#09090B,#E4E4E7,Gallery black + white space
83,Theater/Cinema,#1E1B4B,#312E81,#CA8A04,#0F0F23,#F8FAFC,#4338CA,Dramatic dark + spotlight gold
84,Language Learning App,#4F46E5,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Learning indigo + progress green
85,Coding Bootcamp,#0F172A,#1E293B,#22C55E,#020617,#F8FAFC,#334155,Terminal dark + success green
86,Cybersecurity Platform,#00FF41,#0D0D0D,#FF3333,#000000,#E0E0E0,#1F1F1F,Matrix green + alert red
87,Developer Tool / IDE,#1E293B,#334155,#22C55E,#0F172A,#F8FAFC,#475569,Code dark + run green
88,Biotech / Life Sciences,#0EA5E9,#0284C7,#10B981,#F0F9FF,#0C4A6E,#BAE6FD,DNA blue + life green
89,Space Tech / Aerospace,#F8FAFC,#94A3B8,#3B82F6,#0B0B10,#F8FAFC,#1E293B,Star white + launch blue
90,Architecture / Interior,#171717,#404040,#D4AF37,#FFFFFF,#171717,#E5E5E5,Minimal black + accent gold
91,Quantum Computing,#00FFFF,#7B61FF,#FF00FF,#050510,#E0E0FF,#333344,Quantum cyan + interference purple
92,Biohacking / Longevity,#FF4D4D,#4D94FF,#00E676,#F5F5F7,#1C1C1E,#E5E5EA,Bio red/blue + vitality green
93,Autonomous Systems,#00FF41,#008F11,#FF3333,#0D1117,#E6EDF3,#30363D,Terminal green + alert red
94,Generative AI Art,#18181B,#3F3F46,#EC4899,#FAFAFA,#09090B,#E4E4E7,Canvas neutral + creative pink
95,Spatial / Vision OS,#FFFFFF,#E5E5E5,#007AFF,#888888,#000000,#CCCCCC,Glass white + system blue
96,Climate Tech,#059669,#10B981,#FBBF24,#ECFDF5,#064E3B,#A7F3D0,Nature green + solar gold
.claude/skills/ui-ux-pro-max/data/icons.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
No,Category,Icon Name,Keywords,Library,Import Code,Usage,Best For,Style
1,Navigation,menu,hamburger menu navigation toggle bars,Lucide,import { Menu } from 'lucide-react',<Menu />,Mobile navigation drawer toggle sidebar,Outline
2,Navigation,arrow-left,back previous return navigate,Lucide,import { ArrowLeft } from 'lucide-react',<ArrowLeft />,Back button breadcrumb navigation,Outline
3,Navigation,arrow-right,next forward continue navigate,Lucide,import { ArrowRight } from 'lucide-react',<ArrowRight />,Forward button next step CTA,Outline
4,Navigation,chevron-down,dropdown expand accordion select,Lucide,import { ChevronDown } from 'lucide-react',<ChevronDown />,Dropdown toggle accordion header,Outline
5,Navigation,chevron-up,collapse close accordion minimize,Lucide,import { ChevronUp } from 'lucide-react',<ChevronUp />,Accordion collapse minimize,Outline
6,Navigation,home,homepage main dashboard start,Lucide,import { Home } from 'lucide-react',<Home />,Home navigation main page,Outline
7,Navigation,x,close cancel dismiss remove exit,Lucide,import { X } from 'lucide-react',<X />,Modal close dismiss button,Outline
8,Navigation,external-link,open new tab external link,Lucide,import { ExternalLink } from 'lucide-react',<ExternalLink />,External link indicator,Outline
9,Action,plus,add create new insert,Lucide,import { Plus } from 'lucide-react',<Plus />,Add button create new item,Outline
10,Action,minus,remove subtract decrease delete,Lucide,import { Minus } from 'lucide-react',<Minus />,Remove item quantity decrease,Outline
11,Action,trash-2,delete remove discard bin,Lucide,import { Trash2 } from 'lucide-react',<Trash2 />,Delete action destructive,Outline
12,Action,edit,pencil modify change update,Lucide,import { Edit } from 'lucide-react',<Edit />,Edit button modify content,Outline
13,Action,save,disk store persist save,Lucide,import { Save } from 'lucide-react',<Save />,Save button persist changes,Outline
14,Action,download,export save file download,Lucide,import { Download } from 'lucide-react',<Download />,Download file export,Outline
15,Action,upload,import file attach upload,Lucide,import { Upload } from 'lucide-react',<Upload />,Upload file import,Outline
16,Action,copy,duplicate clipboard paste,Lucide,import { Copy } from 'lucide-react',<Copy />,Copy to clipboard,Outline
17,Action,share,social distribute send,Lucide,import { Share } from 'lucide-react',<Share />,Share button social,Outline
18,Action,search,find lookup filter query,Lucide,import { Search } from 'lucide-react',<Search />,Search input bar,Outline
19,Action,filter,sort refine narrow options,Lucide,import { Filter } from 'lucide-react',<Filter />,Filter dropdown sort,Outline
20,Action,settings,gear cog preferences config,Lucide,import { Settings } from 'lucide-react',<Settings />,Settings page configuration,Outline
21,Status,check,success done complete verified,Lucide,import { Check } from 'lucide-react',<Check />,Success state checkmark,Outline
22,Status,check-circle,success verified approved complete,Lucide,import { CheckCircle } from 'lucide-react',<CheckCircle />,Success badge verified,Outline
23,Status,x-circle,error failed cancel rejected,Lucide,import { XCircle } from 'lucide-react',<XCircle />,Error state failed,Outline
24,Status,alert-triangle,warning caution attention danger,Lucide,import { AlertTriangle } from 'lucide-react',<AlertTriangle />,Warning message caution,Outline
25,Status,alert-circle,info notice information help,Lucide,import { AlertCircle } from 'lucide-react',<AlertCircle />,Info notice alert,Outline
26,Status,info,information help tooltip details,Lucide,import { Info } from 'lucide-react',<Info />,Information tooltip help,Outline
27,Status,loader,loading spinner processing wait,Lucide,import { Loader } from 'lucide-react',<Loader className="animate-spin" />,Loading state spinner,Outline
28,Status,clock,time schedule pending wait,Lucide,import { Clock } from 'lucide-react',<Clock />,Pending time schedule,Outline
29,Communication,mail,email message inbox letter,Lucide,import { Mail } from 'lucide-react',<Mail />,Email contact inbox,Outline
30,Communication,message-circle,chat comment bubble conversation,Lucide,import { MessageCircle } from 'lucide-react',<MessageCircle />,Chat comment message,Outline
31,Communication,phone,call mobile telephone contact,Lucide,import { Phone } from 'lucide-react',<Phone />,Phone contact call,Outline
32,Communication,send,submit dispatch message airplane,Lucide,import { Send } from 'lucide-react',<Send />,Send message submit,Outline
33,Communication,bell,notification alert ring reminder,Lucide,import { Bell } from 'lucide-react',<Bell />,Notification bell alert,Outline
34,User,user,profile account person avatar,Lucide,import { User } from 'lucide-react',<User />,User profile account,Outline
35,User,users,team group people members,Lucide,import { Users } from 'lucide-react',<Users />,Team group members,Outline
36,User,user-plus,add invite new member,Lucide,import { UserPlus } from 'lucide-react',<UserPlus />,Add user invite,Outline
37,User,log-in,signin authenticate enter,Lucide,import { LogIn } from 'lucide-react',<LogIn />,Login signin,Outline
38,User,log-out,signout exit leave logout,Lucide,import { LogOut } from 'lucide-react',<LogOut />,Logout signout,Outline
39,Media,image,photo picture gallery thumbnail,Lucide,import { Image } from 'lucide-react',<Image />,Image photo gallery,Outline
40,Media,video,movie film play record,Lucide,import { Video } from 'lucide-react',<Video />,Video player media,Outline
41,Media,play,start video audio media,Lucide,import { Play } from 'lucide-react',<Play />,Play button video audio,Outline
42,Media,pause,stop halt video audio,Lucide,import { Pause } from 'lucide-react',<Pause />,Pause button media,Outline
43,Media,volume-2,sound audio speaker music,Lucide,import { Volume2 } from 'lucide-react',<Volume2 />,Volume audio sound,Outline
44,Media,mic,microphone record voice audio,Lucide,import { Mic } from 'lucide-react',<Mic />,Microphone voice record,Outline
45,Media,camera,photo capture snapshot picture,Lucide,import { Camera } from 'lucide-react',<Camera />,Camera photo capture,Outline
46,Commerce,shopping-cart,cart checkout basket buy,Lucide,import { ShoppingCart } from 'lucide-react',<ShoppingCart />,Shopping cart e-commerce,Outline
47,Commerce,shopping-bag,purchase buy store bag,Lucide,import { ShoppingBag } from 'lucide-react',<ShoppingBag />,Shopping bag purchase,Outline
48,Commerce,credit-card,payment card checkout stripe,Lucide,import { CreditCard } from 'lucide-react',<CreditCard />,Payment credit card,Outline
49,Commerce,dollar-sign,money price currency cost,Lucide,import { DollarSign } from 'lucide-react',<DollarSign />,Price money currency,Outline
50,Commerce,tag,label price discount sale,Lucide,import { Tag } from 'lucide-react',<Tag />,Price tag label,Outline
51,Commerce,gift,present reward bonus offer,Lucide,import { Gift } from 'lucide-react',<Gift />,Gift reward offer,Outline
52,Commerce,percent,discount sale offer promo,Lucide,import { Percent } from 'lucide-react',<Percent />,Discount percentage sale,Outline
53,Data,bar-chart,analytics statistics graph metrics,Lucide,import { BarChart } from 'lucide-react',<BarChart />,Bar chart analytics,Outline
54,Data,pie-chart,statistics distribution breakdown,Lucide,import { PieChart } from 'lucide-react',<PieChart />,Pie chart distribution,Outline
55,Data,trending-up,growth increase positive trend,Lucide,import { TrendingUp } from 'lucide-react',<TrendingUp />,Growth trend positive,Outline
56,Data,trending-down,decline decrease negative trend,Lucide,import { TrendingDown } from 'lucide-react',<TrendingDown />,Decline trend negative,Outline
57,Data,activity,pulse heartbeat monitor live,Lucide,import { Activity } from 'lucide-react',<Activity />,Activity monitor pulse,Outline
58,Data,database,storage server data backend,Lucide,import { Database } from 'lucide-react',<Database />,Database storage,Outline
59,Files,file,document page paper doc,Lucide,import { File } from 'lucide-react',<File />,File document,Outline
60,Files,file-text,document text page article,Lucide,import { FileText } from 'lucide-react',<FileText />,Text document article,Outline
61,Files,folder,directory organize group files,Lucide,import { Folder } from 'lucide-react',<Folder />,Folder directory,Outline
62,Files,folder-open,expanded browse files view,Lucide,import { FolderOpen } from 'lucide-react',<FolderOpen />,Open folder browse,Outline
63,Files,paperclip,attachment attach file link,Lucide,import { Paperclip } from 'lucide-react',<Paperclip />,Attachment paperclip,Outline
64,Files,link,url hyperlink chain connect,Lucide,import { Link } from 'lucide-react',<Link />,Link URL hyperlink,Outline
65,Files,clipboard,paste copy buffer notes,Lucide,import { Clipboard } from 'lucide-react',<Clipboard />,Clipboard paste,Outline
66,Layout,grid,tiles gallery layout dashboard,Lucide,import { Grid } from 'lucide-react',<Grid />,Grid layout gallery,Outline
67,Layout,list,rows table lines items,Lucide,import { List } from 'lucide-react',<List />,List view rows,Outline
68,Layout,columns,layout split dual sidebar,Lucide,import { Columns } from 'lucide-react',<Columns />,Column layout split,Outline
69,Layout,maximize,fullscreen expand enlarge zoom,Lucide,import { Maximize } from 'lucide-react',<Maximize />,Fullscreen maximize,Outline
70,Layout,minimize,reduce shrink collapse exit,Lucide,import { Minimize } from 'lucide-react',<Minimize />,Minimize reduce,Outline
71,Layout,sidebar,panel drawer navigation menu,Lucide,import { Sidebar } from 'lucide-react',<Sidebar />,Sidebar panel,Outline
72,Social,heart,like love favorite wishlist,Lucide,import { Heart } from 'lucide-react',<Heart />,Like favorite love,Outline
73,Social,star,rating review favorite bookmark,Lucide,import { Star } from 'lucide-react',<Star />,Star rating favorite,Outline
74,Social,thumbs-up,like approve agree positive,Lucide,import { ThumbsUp } from 'lucide-react',<ThumbsUp />,Like approve thumb,Outline
75,Social,thumbs-down,dislike disapprove disagree negative,Lucide,import { ThumbsDown } from 'lucide-react',<ThumbsDown />,Dislike disapprove,Outline
76,Social,bookmark,save later favorite mark,Lucide,import { Bookmark } from 'lucide-react',<Bookmark />,Bookmark save,Outline
77,Social,flag,report mark important highlight,Lucide,import { Flag } from 'lucide-react',<Flag />,Flag report,Outline
78,Device,smartphone,mobile phone device touch,Lucide,import { Smartphone } from 'lucide-react',<Smartphone />,Mobile smartphone,Outline
79,Device,tablet,ipad device touch screen,Lucide,import { Tablet } from 'lucide-react',<Tablet />,Tablet device,Outline
80,Device,monitor,desktop screen computer display,Lucide,import { Monitor } from 'lucide-react',<Monitor />,Desktop monitor,Outline
81,Device,laptop,notebook computer portable device,Lucide,import { Laptop } from 'lucide-react',<Laptop />,Laptop computer,Outline
82,Device,printer,print document output paper,Lucide,import { Printer } from 'lucide-react',<Printer />,Printer print,Outline
83,Security,lock,secure password protected private,Lucide,import { Lock } from 'lucide-react',<Lock />,Lock secure,Outline
84,Security,unlock,open access unsecure public,Lucide,import { Unlock } from 'lucide-react',<Unlock />,Unlock open,Outline
85,Security,shield,protection security safe guard,Lucide,import { Shield } from 'lucide-react',<Shield />,Shield protection,Outline
86,Security,key,password access unlock login,Lucide,import { Key } from 'lucide-react',<Key />,Key password,Outline
87,Security,eye,view show visible password,Lucide,import { Eye } from 'lucide-react',<Eye />,Show password view,Outline
88,Security,eye-off,hide invisible password hidden,Lucide,import { EyeOff } from 'lucide-react',<EyeOff />,Hide password,Outline
89,Location,map-pin,location marker place address,Lucide,import { MapPin } from 'lucide-react',<MapPin />,Location pin marker,Outline
90,Location,map,directions navigate geography location,Lucide,import { Map } from 'lucide-react',<Map />,Map directions,Outline
91,Location,navigation,compass direction pointer arrow,Lucide,import { Navigation } from 'lucide-react',<Navigation />,Navigation compass,Outline
92,Location,globe,world international global web,Lucide,import { Globe } from 'lucide-react',<Globe />,Globe world,Outline
93,Time,calendar,date schedule event appointment,Lucide,import { Calendar } from 'lucide-react',<Calendar />,Calendar date,Outline
94,Time,refresh-cw,reload sync update refresh,Lucide,import { RefreshCw } from 'lucide-react',<RefreshCw />,Refresh reload,Outline
95,Time,rotate-ccw,undo back revert history,Lucide,import { RotateCcw } from 'lucide-react',<RotateCcw />,Undo revert,Outline
96,Time,rotate-cw,redo forward repeat history,Lucide,import { RotateCw } from 'lucide-react',<RotateCw />,Redo forward,Outline
97,Development,code,develop programming syntax html,Lucide,import { Code } from 'lucide-react',<Code />,Code development,Outline
98,Development,terminal,console cli command shell,Lucide,import { Terminal } from 'lucide-react',<Terminal />,Terminal console,Outline
99,Development,git-branch,version control branch merge,Lucide,import { GitBranch } from 'lucide-react',<GitBranch />,Git branch,Outline
100,Development,github,repository code open source,Lucide,import { Github } from 'lucide-react',<Github />,GitHub repository,Outline
.claude/skills/ui-ux-pro-max/data/landing.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
No,Pattern Name,Keywords,Section Order,Primary CTA Placement,Color Strategy,Recommended Effects,Conversion Optimization
1,Hero + Features + CTA,"hero, hero-centric, features, feature-rich, cta, call-to-action","1. Hero with headline/image, 2. Value prop, 3. Key features (3-5), 4. CTA section, 5. Footer",Hero (sticky) + Bottom,Hero: Brand primary or vibrant. Features: Card bg #FAFAFA. CTA: Contrasting accent color,"Hero parallax, feature card hover lift, CTA glow on hover",Deep CTA placement. Use contrasting color (at least 7:1 contrast ratio). Sticky navbar CTA.
2,Hero + Testimonials + CTA,"hero, testimonials, social-proof, trust, reviews, cta","1. Hero, 2. Problem statement, 3. Solution overview, 4. Testimonials carousel, 5. CTA",Hero (sticky) + Post-testimonials,"Hero: Brand color. Testimonials: Light bg #F5F5F5. Quotes: Italic, muted color #666. CTA: Vibrant","Testimonial carousel slide animations, quote marks animations, avatar fade-in",Social proof before CTA. Use 3-5 testimonials. Include photo + name + role. CTA after social proof.
3,Product Demo + Features,"demo, product-demo, features, showcase, interactive","1. Hero, 2. Product video/mockup (center), 3. Feature breakdown per section, 4. Comparison (optional), 5. CTA",Video center + CTA right/bottom,Video surround: Brand color overlay. Features: Icon color #0080FF. Text: Dark #222,"Video play button pulse, feature scroll reveals, demo interaction highlights",Embedded product demo increases engagement. Use interactive mockup if possible. Auto-play video muted.
4,Minimal Single Column,"minimal, simple, direct, single-column, clean","1. Hero headline, 2. Short description, 3. Benefit bullets (3 max), 4. CTA, 5. Footer","Center, large CTA button",Minimalist: Brand + white #FFFFFF + accent. Buttons: High contrast 7:1+. Text: Black/Dark grey,Minimal hover effects. Smooth scroll. CTA scale on hover (subtle),Single CTA focus. Large typography. Lots of whitespace. No nav clutter. Mobile-first.
5,Funnel (3-Step Conversion),"funnel, conversion, steps, wizard, onboarding","1. Hero, 2. Step 1 (problem), 3. Step 2 (solution), 4. Step 3 (action), 5. CTA progression",Each step: mini-CTA. Final: main CTA,"Step colors: 1 (Red/Problem), 2 (Orange/Process), 3 (Green/Solution). CTA: Brand color","Step number animations, progress bar fill, step transitions smooth scroll",Progressive disclosure. Show only essential info per step. Use progress indicators. Multiple CTAs.
6,Comparison Table + CTA,"comparison, table, compare, versus, cta","1. Hero, 2. Problem intro, 3. Comparison table (product vs competitors), 4. Pricing (optional), 5. CTA",Table: Right column. CTA: Below table,Table: Alternating rows (white/light grey). Your product: Highlight #FFFACD (light yellow) or green. Text: Dark,"Table row hover highlight, price toggle animations, feature checkmark animations",Use comparison to show unique value. Highlight your product row. Include 'free trial' in pricing row.
7,Lead Magnet + Form,"lead, form, signup, capture, email, magnet","1. Hero (benefit headline), 2. Lead magnet preview (ebook cover, checklist, etc), 3. Form (minimal fields), 4. CTA submit",Form CTA: Submit button,Lead magnet: Professional design. Form: Clean white bg. Inputs: Light border #CCCCCC. CTA: Brand color,"Form focus state animations, input validation animations, success confirmation animation",Form fields â‰¤ 3 for best conversion. Offer valuable lead magnet preview. Show form submission progress.
8,Pricing Page + CTA,"pricing, plans, tiers, comparison, cta","1. Hero (pricing headline), 2. Price comparison cards, 3. Feature comparison table, 4. FAQ section, 5. Final CTA",Each card: CTA button. Sticky CTA in nav,"Free: Grey, Starter: Blue, Pro: Green/Gold, Enterprise: Dark. Cards: 1px border, shadow","Price toggle animation (monthly/yearly), card comparison highlight, FAQ accordion open/close",Recommend starter plan (pre-select/highlight). Show annual discount (20-30%). Use FAQs to address concerns.
9,Video-First Hero,"video, hero, media, visual, engaging","1. Hero with video background, 2. Key features overlay, 3. Benefits section, 4. CTA",Overlay on video (center/bottom) + Bottom section,Dark overlay 60% on video. Brand accent for CTA. White text on dark.,"Video autoplay muted, parallax scroll, text fade-in on scroll",86% higher engagement with video. Add captions for accessibility. Compress video for performance.
10,Scroll-Triggered Storytelling,"storytelling, scroll, narrative, story, immersive","1. Intro hook, 2. Chapter 1 (problem), 3. Chapter 2 (journey), 4. Chapter 3 (solution), 5. Climax CTA",End of each chapter (mini) + Final climax CTA,Progressive reveal. Each chapter has distinct color. Building intensity.,"ScrollTrigger animations, parallax layers, progressive disclosure, chapter transitions",Narrative increases time-on-page 3x. Use progress indicator. Mobile: simplify animations.
11,AI Personalization Landing,"ai, personalization, smart, recommendation, dynamic","1. Dynamic hero (personalized), 2. Relevant features, 3. Tailored testimonials, 4. Smart CTA",Context-aware placement based on user segment,Adaptive based on user data. A/B test color variations per segment.,"Dynamic content swap, fade transitions, personalized product recommendations",20%+ conversion with personalization. Requires analytics integration. Fallback for new users.
12,Waitlist/Coming Soon,"waitlist, coming-soon, launch, early-access, notify","1. Hero with countdown, 2. Product teaser/preview, 3. Email capture form, 4. Social proof (waitlist count)",Email form prominent (above fold) + Sticky form on scroll,Anticipation: Dark + accent highlights. Countdown in brand color. Urgency indicators.,"Countdown timer animation, email validation feedback, success confetti, social share buttons",Scarcity + exclusivity. Show waitlist count. Early access benefits. Referral program.
13,Comparison Table Focus,"comparison, table, versus, compare, features","1. Hero (problem statement), 2. Comparison matrix (you vs competitors), 3. Feature deep-dive, 4. Winner CTA",After comparison table (highlighted row) + Bottom,Your product column highlighted (accent bg or green). Competitors neutral. Checkmarks green.,"Table row hover highlight, feature checkmark animations, sticky comparison header",Show value vs competitors. 35% higher conversion. Be factual. Include pricing if favorable.
14,Pricing-Focused Landing,"pricing, price, cost, plans, subscription","1. Hero (value proposition), 2. Pricing cards (3 tiers), 3. Feature comparison, 4. FAQ, 5. Final CTA",Each pricing card + Sticky CTA in nav + Bottom,Popular plan highlighted (brand color border/bg). Free: grey. Enterprise: dark/premium.,"Price toggle monthly/annual animation, card hover lift, FAQ accordion smooth open",Annual discount 20-30%. Recommend mid-tier (most popular badge). Address objections in FAQ.
15,App Store Style Landing,"app, mobile, download, store, install","1. Hero with device mockup, 2. Screenshots carousel, 3. Features with icons, 4. Reviews/ratings, 5. Download CTAs",Download buttons prominent (App Store + Play Store) throughout,Dark/light matching app store feel. Star ratings in gold. Screenshots with device frames.,"Device mockup rotations, screenshot slider, star rating animations, download button pulse",Show real screenshots. Include ratings (4.5+ stars). QR code for mobile. Platform-specific CTAs.
16,FAQ/Documentation Landing,"faq, documentation, help, support, questions","1. Hero with search bar, 2. Popular categories, 3. FAQ accordion, 4. Contact/support CTA",Search bar prominent + Contact CTA for unresolved questions,"Clean, high readability. Minimal color. Category icons in brand color. Success green for resolved.","Search autocomplete, smooth accordion open/close, category hover, helpful feedback buttons",Reduce support tickets. Track search analytics. Show related articles. Contact escalation path.
17,Immersive/Interactive Experience,"immersive, interactive, experience, 3d, animation","1. Full-screen interactive element, 2. Guided product tour, 3. Key benefits revealed, 4. CTA after completion",After interaction complete + Skip option for impatient users,Immersive experience colors. Dark background for focus. Highlight interactive elements.,"WebGL, 3D interactions, gamification elements, progress indicators, reward animations",40% higher engagement. Performance trade-off. Provide skip option. Mobile fallback essential.
18,Event/Conference Landing,"event, conference, meetup, registration, schedule","1. Hero (date/location/countdown), 2. Speakers grid, 3. Agenda/schedule, 4. Sponsors, 5. Register CTA",Register CTA sticky + After speakers + Bottom,Urgency colors (countdown). Event branding. Speaker cards professional. Sponsor logos neutral.,"Countdown timer, speaker hover cards with bio, agenda tabs, early bird countdown",Early bird pricing with deadline. Social proof (past attendees). Speaker credibility. Multi-ticket discounts.
19,Product Review/Ratings Focused,"reviews, ratings, testimonials, social-proof, stars","1. Hero (product + aggregate rating), 2. Rating breakdown, 3. Individual reviews, 4. Buy/CTA",After reviews summary + Buy button alongside reviews,Trust colors. Star ratings gold. Verified badge green. Review sentiment colors.,"Star fill animations, review filtering, helpful vote interactions, photo lightbox",User-generated content builds trust. Show verified purchases. Filter by rating. Respond to negative reviews.
20,Community/Forum Landing,"community, forum, social, members, discussion","1. Hero (community value prop), 2. Popular topics/categories, 3. Active members showcase, 4. Join CTA",Join button prominent + After member showcase,"Warm, welcoming. Member photos add humanity. Topic badges in brand colors. Activity indicators green.","Member avatars animation, activity feed live updates, topic hover previews, join success celebration","Show active community (member count, posts today). Highlight benefits. Preview content. Easy onboarding."
21,Before-After Transformation,"before-after, transformation, results, comparison","1. Hero (problem state), 2. Transformation slider/comparison, 3. How it works, 4. Results CTA",After transformation reveal + Bottom,Contrast: muted/grey (before) vs vibrant/colorful (after). Success green for results.,"Slider comparison interaction, before/after reveal animations, result counters, testimonial videos",Visual proof of value. 45% higher conversion. Real results. Specific metrics. Guarantee offer.
22,Marketplace / Directory,"marketplace, directory, search, listing","1. Hero (Search focused), 2. Categories, 3. Featured Listings, 4. Trust/Safety, 5. CTA (Become a host/seller)",Hero Search Bar + Navbar 'List your item',Search: High contrast. Categories: Visual icons. Trust: Blue/Green.,Search autocomplete animation," map hover pins,  card carousel, Search bar is the CTA. Reduce friction to search. Popular searches suggestions."
23,Newsletter / Content First,"newsletter, content, writer, blog, subscribe","1. Hero (Value Prop + Form), 2. Recent Issues/Archives, 3. Social Proof (Subscriber count), 4. About Author",Hero inline form + Sticky header form,Minimalist. Paper-like background. Text focus. Accent color for Subscribe.,Text highlight animations," typewriter effect,  subtle fade-in, Single field form (Email only). Show 'Join X, 000 readers'. Read sample link."
24,Webinar Registration,"webinar, registration, event, training, live","1. Hero (Topic + Timer + Form), 2. What you'll learn, 3. Speaker Bio, 4. Urgency/Bonuses, 5. Form (again)",Hero (Right side form) + Bottom anchor,Urgency: Red/Orange. Professional: Blue/Navy. Form: High contrast white.,Countdown timer," speaker avatar float,  urgent ticker, Limited seats logic. 'Live' indicator. Auto-fill timezone."
25,Enterprise Gateway,"enterprise, corporate, gateway, solutions, portal","1. Hero (Video/Mission), 2. Solutions by Industry, 3. Solutions by Role, 4. Client Logos, 5. Contact Sales",Contact Sales (Primary) + Login (Secondary),Corporate: Navy/Grey. High integrity. Conservative accents.,Slow video background," logo carousel,  tab switching for industries, Path selection (I am a...). Mega menu navigation. Trust signals prominent."
26,Portfolio Grid,"portfolio, grid, showcase, gallery, masonry","1. Hero (Name/Role), 2. Project Grid (Masonry), 3. About/Philosophy, 4. Contact",Project Card Hover + Footer Contact,Neutral background (let work shine). Text: Black/White. Accent: Minimal.,Image lazy load reveal," hover overlay info,  lightbox view, Visuals first. Filter by category. Fast loading essential."
27,Horizontal Scroll Journey,"horizontal, scroll, journey, gallery, storytelling, panoramic","1. Intro (Vertical), 2. The Journey (Horizontal Track), 3. Detail Reveal, 4. Vertical Footer",Floating Sticky CTA or End of Horizontal Track,Continuous palette transition. Chapter colors. Progress bar #000000.,"Scroll-jacking (careful), parallax layers, horizontal slide, progress indicator","Immersive product discovery. High engagement. Keep navigation visible.
28,Bento Grid Showcase,bento,  grid,  features,  modular,  apple-style,  showcase"", 1. Hero, 2. Bento Grid (Key Features), 3. Detail Cards, 4. Tech Specs, 5. CTA, Floating Action Button or Bottom of Grid, Card backgrounds: #F5F5F7 or Glass. Icons: Vibrant brand colors. Text: Dark., Hover card scale (1.02), video inside cards, tilt effect, staggered reveal, Scannable value props. High information density without clutter. Mobile stack.
29,Interactive 3D Configurator,3d,  configurator,  customizer,  interactive,  product"", 1. Hero (Configurator), 2. Feature Highlight (synced), 3. Price/Specs, 4. Purchase, Inside Configurator UI + Sticky Bottom Bar, Neutral studio background. Product: Realistic materials. UI: Minimal overlay., Real-time rendering, material swap animation, camera rotate/zoom, light reflection, Increases ownership feeling. 360 view reduces return rates. Direct add-to-cart.
30,AI-Driven Dynamic Landing,ai,  dynamic,  personalized,  adaptive,  generative"", 1. Prompt/Input Hero, 2. Generated Result Preview, 3. How it Works, 4. Value Prop, Input Field (Hero) + 'Try it' Buttons, Adaptive to user input. Dark mode for compute feel. Neon accents., Typing text effects, shimmering generation loaders, morphing layouts, Immediate value demonstration. 'Show, don't tell'. Low friction start."
.claude/skills/ui-ux-pro-max/data/products.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
No,Product Type,Keywords,Primary Style Recommendation,Secondary Styles,Landing Page Pattern,Dashboard Style (if applicable),Color Palette Focus,Key Considerations
1,SaaS (General),"app, b2b, cloud, general, saas, software, subscription",Glassmorphism + Flat Design,"Soft UI Evolution, Minimalism",Hero + Features + CTA,Data-Dense + Real-Time Monitoring,Trust blue + accent contrast,Balance modern feel with clarity. Focus on CTAs.
2,Micro SaaS,"app, b2b, cloud, indie, micro, micro-saas, niche, saas, small, software, solo, subscription",Flat Design + Vibrant & Block,"Motion-Driven, Micro-interactions",Minimal & Direct + Demo,Executive Dashboard,Vibrant primary + white space,"Keep simple, show product quickly. Speed is key."
3,E-commerce,"buy, commerce, e, ecommerce, products, retail, sell, shop, store",Vibrant & Block-based,"Aurora UI, Motion-Driven",Feature-Rich Showcase,Sales Intelligence Dashboard,Brand primary + success green,Engagement & conversions. High visual hierarchy.
4,E-commerce Luxury,"buy, commerce, e, ecommerce, elegant, exclusive, high-end, luxury, premium, products, retail, sell, shop, store",Liquid Glass + Glassmorphism,"3D & Hyperrealism, Aurora UI",Feature-Rich Showcase,Sales Intelligence Dashboard,Premium colors + minimal accent,Elegance & sophistication. Premium materials.
5,Service Landing Page,"appointment, booking, consultation, conversion, landing, marketing, page, service",Hero-Centric + Trust & Authority,"Social Proof-Focused, Storytelling",Hero-Centric Design,N/A - Analytics for conversions,Brand primary + trust colors,Social proof essential. Show expertise.
6,B2B Service,"appointment, b, b2b, booking, business, consultation, corporate, enterprise, service",Trust & Authority + Minimal,"Feature-Rich, Conversion-Optimized",Feature-Rich Showcase,Sales Intelligence Dashboard,Professional blue + neutral grey,Credibility essential. Clear ROI messaging.
7,Financial Dashboard,"admin, analytics, dashboard, data, financial, panel",Dark Mode (OLED) + Data-Dense,"Minimalism, Accessible & Ethical",N/A - Dashboard focused,Financial Dashboard,Dark bg + red/green alerts + trust blue,"High contrast, real-time updates, accuracy paramount."
8,Analytics Dashboard,"admin, analytics, dashboard, data, panel",Data-Dense + Heat Map & Heatmap,"Minimalism, Dark Mode (OLED)",N/A - Analytics focused,Drill-Down Analytics + Comparative,Cool→Hot gradients + neutral grey,Clarity > aesthetics. Color-coded data priority.
9,Healthcare App,"app, clinic, health, healthcare, medical, patient",Neumorphism + Accessible & Ethical,"Soft UI Evolution, Claymorphism (for patients)",Social Proof-Focused,User Behavior Analytics,Calm blue + health green + trust,Accessibility mandatory. Calming aesthetic.
10,Educational App,"app, course, education, educational, learning, school, training",Claymorphism + Micro-interactions,"Vibrant & Block-based, Flat Design",Storytelling-Driven,User Behavior Analytics,Playful colors + clear hierarchy,Engagement & ease of use. Age-appropriate design.
11,Creative Agency,"agency, creative, design, marketing, studio",Brutalism + Motion-Driven,"Retro-Futurism, Storytelling-Driven",Storytelling-Driven,N/A - Portfolio focused,Bold primaries + artistic freedom,Differentiation key. Wow-factor necessary.
12,Portfolio/Personal,"creative, personal, portfolio, projects, showcase, work",Motion-Driven + Minimalism,"Brutalism, Aurora UI",Storytelling-Driven,N/A - Personal branding,Brand primary + artistic interpretation,Showcase work. Personality shine through.
13,Gaming,"entertainment, esports, game, gaming, play",3D & Hyperrealism + Retro-Futurism,"Motion-Driven, Vibrant & Block",Feature-Rich Showcase,N/A - Game focused,Vibrant + neon + immersive colors,Immersion priority. Performance critical.
14,Government/Public Service,"appointment, booking, consultation, government, public, service",Accessible & Ethical + Minimalism,"Flat Design, Inclusive Design",Minimal & Direct,Executive Dashboard,Professional blue + high contrast,WCAG AAA mandatory. Trust paramount.
15,Fintech/Crypto,"banking, blockchain, crypto, defi, finance, fintech, money, nft, payment, web3",Glassmorphism + Dark Mode (OLED),"Retro-Futurism, Motion-Driven",Conversion-Optimized,Real-Time Monitoring + Predictive,Dark tech colors + trust + vibrant accents,Security perception. Real-time data critical.
16,Social Media App,"app, community, content, entertainment, media, network, sharing, social, streaming, users, video",Vibrant & Block-based + Motion-Driven,"Aurora UI, Micro-interactions",Feature-Rich Showcase,User Behavior Analytics,Vibrant + engagement colors,Engagement & retention. Addictive design ethics.
17,Productivity Tool,"collaboration, productivity, project, task, tool, workflow",Flat Design + Micro-interactions,"Minimalism, Soft UI Evolution",Interactive Product Demo,Drill-Down Analytics,Clear hierarchy + functional colors,Ease of use. Speed & efficiency focus.
18,Design System/Component Library,"component, design, library, system",Minimalism + Accessible & Ethical,"Flat Design, Zero Interface",Feature-Rich Showcase,N/A - Dev focused,Clear hierarchy + code-like structure,Consistency. Developer-first approach.
19,AI/Chatbot Platform,"ai, artificial-intelligence, automation, chatbot, machine-learning, ml, platform",AI-Native UI + Minimalism,"Zero Interface, Glassmorphism",Interactive Product Demo,AI/ML Analytics Dashboard,Neutral + AI Purple (#6366F1),Conversational UI. Streaming text. Context awareness. Minimal chrome.
20,NFT/Web3 Platform,"nft, platform, web",Cyberpunk UI + Glassmorphism,"Aurora UI, 3D & Hyperrealism",Feature-Rich Showcase,Crypto/Blockchain Dashboard,Dark + Neon + Gold (#FFD700),Wallet integration. Transaction feedback. Gas fees display. Dark mode essential.
21,Creator Economy Platform,"creator, economy, platform",Vibrant & Block-based + Bento Box Grid,"Motion-Driven, Aurora UI",Social Proof-Focused,User Behavior Analytics,Vibrant + Brand colors,Creator profiles. Monetization display. Engagement metrics. Social proof.
22,Sustainability/ESG Platform,"ai, artificial-intelligence, automation, esg, machine-learning, ml, platform, sustainability",Organic Biophilic + Minimalism,"Accessible & Ethical, Flat Design",Trust & Authority,Energy/Utilities Dashboard,Green (#228B22) + Earth tones,Carbon footprint visuals. Progress indicators. Certification badges. Eco-friendly imagery.
23,Remote Work/Collaboration Tool,"collaboration, remote, tool, work",Soft UI Evolution + Minimalism,"Glassmorphism, Micro-interactions",Feature-Rich Showcase,Drill-Down Analytics,Calm Blue + Neutral grey,Real-time collaboration. Status indicators. Video integration. Notification management.
24,Mental Health App,"app, health, mental",Neumorphism + Accessible & Ethical,"Claymorphism, Soft UI Evolution",Social Proof-Focused,Healthcare Analytics,Calm Pastels + Trust colors,Calming aesthetics. Privacy-first. Crisis resources. Progress tracking. Accessibility mandatory.
25,Pet Tech App,"app, pet, tech",Claymorphism + Vibrant & Block-based,"Micro-interactions, Flat Design",Storytelling-Driven,User Behavior Analytics,Playful + Warm colors,Pet profiles. Health tracking. Playful UI. Photo galleries. Vet integration.
26,Smart Home/IoT Dashboard,"admin, analytics, dashboard, data, home, iot, panel, smart",Glassmorphism + Dark Mode (OLED),"Minimalism, AI-Native UI",Interactive Product Demo,Real-Time Monitoring,Dark + Status indicator colors,Device status. Real-time controls. Energy monitoring. Automation rules. Quick actions.
27,EV/Charging Ecosystem,"charging, ecosystem, ev",Minimalism + Aurora UI,"Glassmorphism, Organic Biophilic",Hero-Centric Design,Energy/Utilities Dashboard,Electric Blue (#009CD1) + Green,Charging station maps. Range estimation. Cost calculation. Environmental impact.
28,Subscription Box Service,"appointment, booking, box, consultation, membership, plan, recurring, service, subscription",Vibrant & Block-based + Motion-Driven,"Claymorphism, Aurora UI",Feature-Rich Showcase,E-commerce Analytics,Brand + Excitement colors,Unboxing experience. Personalization quiz. Subscription management. Product reveals.
29,Podcast Platform,"platform, podcast",Dark Mode (OLED) + Minimalism,"Motion-Driven, Vibrant & Block-based",Storytelling-Driven,Media/Entertainment Dashboard,Dark + Audio waveform accents,Audio player UX. Episode discovery. Creator tools. Analytics for podcasters.
30,Dating App,"app, dating",Vibrant & Block-based + Motion-Driven,"Aurora UI, Glassmorphism",Social Proof-Focused,User Behavior Analytics,Warm + Romantic (Pink/Red gradients),Profile cards. Swipe interactions. Match animations. Safety features. Video chat.
31,Micro-Credentials/Badges Platform,"badges, credentials, micro, platform",Minimalism + Flat Design,"Accessible & Ethical, Swiss Modernism 2.0",Trust & Authority,Education Dashboard,Trust Blue + Gold (#FFD700),Credential verification. Badge display. Progress tracking. Issuer trust. LinkedIn integration.
32,Knowledge Base/Documentation,"base, documentation, knowledge",Minimalism + Accessible & Ethical,"Swiss Modernism 2.0, Flat Design",FAQ/Documentation,N/A - Documentation focused,Clean hierarchy + minimal color,Search-first. Clear navigation. Code highlighting. Version switching. Feedback system.
33,Hyperlocal Services,"appointment, booking, consultation, hyperlocal, service, services",Minimalism + Vibrant & Block-based,"Micro-interactions, Flat Design",Conversion-Optimized,Drill-Down Analytics + Map,Location markers + Trust colors,Map integration. Service categories. Provider profiles. Booking system. Reviews.
34,Beauty/Spa/Wellness Service,"appointment, beauty, booking, consultation, service, spa, wellness",Soft UI Evolution + Neumorphism,"Glassmorphism, Minimalism",Hero-Centric Design + Social Proof,User Behavior Analytics,Soft pastels (Pink #FFB6C1 Sage #90EE90) + Cream + Gold accents,Calming aesthetic. Booking system. Service menu. Before/after gallery. Testimonials. Relaxing imagery.
35,Luxury/Premium Brand,"brand, elegant, exclusive, high-end, luxury, premium",Liquid Glass + Glassmorphism,"Minimalism, 3D & Hyperrealism",Storytelling-Driven + Feature-Rich,Sales Intelligence Dashboard,Black + Gold (#FFD700) + White + Minimal accent,Elegance paramount. Premium imagery. Storytelling. High-quality visuals. Exclusive feel.
36,Restaurant/Food Service,"appointment, booking, consultation, delivery, food, menu, order, restaurant, service",Vibrant & Block-based + Motion-Driven,"Claymorphism, Flat Design",Hero-Centric Design + Conversion,N/A - Booking focused,Warm colors (Orange Red Brown) + appetizing imagery,Menu display. Online ordering. Reservation system. Food photography. Location/hours prominent.
37,Fitness/Gym App,"app, exercise, fitness, gym, health, workout",Vibrant & Block-based + Dark Mode (OLED),"Motion-Driven, Neumorphism",Feature-Rich Showcase,User Behavior Analytics,Energetic (Orange #FF6B35 Electric Blue) + Dark bg,Progress tracking. Workout plans. Community features. Achievements. Motivational design.
38,Real Estate/Property,"buy, estate, housing, property, real, real-estate, rent",Glassmorphism + Minimalism,"Motion-Driven, 3D & Hyperrealism",Hero-Centric Design + Feature-Rich,Sales Intelligence Dashboard,Trust Blue (#0077B6) + Gold accents + White,Property listings. Virtual tours. Map integration. Agent profiles. Mortgage calculator. High-quality imagery.
39,Travel/Tourism Agency,"agency, booking, creative, design, flight, hotel, marketing, studio, tourism, travel, vacation",Aurora UI + Motion-Driven,"Vibrant & Block-based, Glassmorphism",Storytelling-Driven + Hero-Centric,Booking Analytics,Vibrant destination colors + Sky Blue + Warm accents,Destination showcase. Booking system. Itinerary builder. Reviews. Inspiration galleries. Mobile-first.
40,Hotel/Hospitality,"hospitality, hotel",Liquid Glass + Minimalism,"Glassmorphism, Soft UI Evolution",Hero-Centric Design + Social Proof,Revenue Management Dashboard,Warm neutrals + Gold (#D4AF37) + Brand accent,Room booking. Amenities showcase. Location maps. Guest reviews. Seasonal pricing. Luxury imagery.
41,Wedding/Event Planning,"conference, event, meetup, planning, registration, ticket, wedding",Soft UI Evolution + Aurora UI,"Glassmorphism, Motion-Driven",Storytelling-Driven + Social Proof,N/A - Planning focused,Soft Pink (#FFD6E0) + Gold + Cream + Sage,Portfolio gallery. Vendor directory. Planning tools. Timeline. Budget tracker. Romantic aesthetic.
42,Legal Services,"appointment, attorney, booking, compliance, consultation, contract, law, legal, service, services",Trust & Authority + Minimalism,"Accessible & Ethical, Swiss Modernism 2.0",Trust & Authority + Minimal,Case Management Dashboard,Navy Blue (#1E3A5F) + Gold + White,Credibility paramount. Practice areas. Attorney profiles. Case results. Contact forms. Professional imagery.
43,Insurance Platform,"insurance, platform",Trust & Authority + Flat Design,"Accessible & Ethical, Minimalism",Conversion-Optimized + Trust,Claims Analytics Dashboard,Trust Blue (#0066CC) + Green (security) + Neutral,Quote calculator. Policy comparison. Claims process. Trust signals. Clear pricing. Security badges.
44,Banking/Traditional Finance,"banking, finance, traditional",Minimalism + Accessible & Ethical,"Trust & Authority, Dark Mode (OLED)",Trust & Authority + Feature-Rich,Financial Dashboard,Navy (#0A1628) + Trust Blue + Gold accents,Security-first. Account overview. Transaction history. Mobile banking. Accessibility critical. Trust paramount.
45,Online Course/E-learning,"course, e, learning, online",Claymorphism + Vibrant & Block-based,"Motion-Driven, Flat Design",Feature-Rich Showcase + Social Proof,Education Dashboard,Vibrant learning colors + Progress green,Course catalog. Progress tracking. Video player. Quizzes. Certificates. Community forums. Gamification.
46,Non-profit/Charity,"charity, non, profit",Accessible & Ethical + Organic Biophilic,"Minimalism, Storytelling-Driven",Storytelling-Driven + Trust,Donation Analytics Dashboard,Cause-related colors + Trust + Warm,Impact stories. Donation flow. Transparency reports. Volunteer signup. Event calendar. Emotional connection.
47,Music Streaming,"music, streaming",Dark Mode (OLED) + Vibrant & Block-based,"Motion-Driven, Aurora UI",Feature-Rich Showcase,Media/Entertainment Dashboard,Dark (#121212) + Vibrant accents + Album art colors,Audio player. Playlist management. Artist pages. Personalization. Social features. Waveform visualizations.
48,Video Streaming/OTT,"ott, streaming, video",Dark Mode (OLED) + Motion-Driven,"Glassmorphism, Vibrant & Block-based",Hero-Centric Design + Feature-Rich,Media/Entertainment Dashboard,Dark bg + Content poster colors + Brand accent,Video player. Content discovery. Watchlist. Continue watching. Personalized recommendations. Thumbnail-heavy.
49,Job Board/Recruitment,"board, job, recruitment",Flat Design + Minimalism,"Vibrant & Block-based, Accessible & Ethical",Conversion-Optimized + Feature-Rich,HR Analytics Dashboard,Professional Blue + Success Green + Neutral,Job listings. Search/filter. Company profiles. Application tracking. Resume upload. Salary insights.
50,Marketplace (P2P),"buyers, listings, marketplace, p, platform, sellers",Vibrant & Block-based + Flat Design,"Micro-interactions, Trust & Authority",Feature-Rich Showcase + Social Proof,E-commerce Analytics,Trust colors + Category colors + Success green,Seller/buyer profiles. Listings. Reviews/ratings. Secure payment. Messaging. Search/filter. Trust badges.
51,Logistics/Delivery,"delivery, logistics",Minimalism + Flat Design,"Dark Mode (OLED), Micro-interactions",Feature-Rich Showcase + Conversion,Real-Time Monitoring + Route Analytics,Blue (#2563EB) + Orange (tracking) + Green (delivered),Real-time tracking. Delivery scheduling. Route optimization. Driver management. Status updates. Map integration.
52,Agriculture/Farm Tech,"agriculture, farm, tech",Organic Biophilic + Flat Design,"Minimalism, Accessible & Ethical",Feature-Rich Showcase + Trust,IoT Sensor Dashboard,Earth Green (#4A7C23) + Brown + Sky Blue,Crop monitoring. Weather data. IoT sensors. Yield tracking. Market prices. Sustainable imagery.
53,Construction/Architecture,"architecture, construction",Minimalism + 3D & Hyperrealism,"Brutalism, Swiss Modernism 2.0",Hero-Centric Design + Feature-Rich,Project Management Dashboard,Grey (#4A4A4A) + Orange (safety) + Blueprint Blue,Project portfolio. 3D renders. Timeline. Material specs. Team collaboration. Blueprint aesthetic.
54,Automotive/Car Dealership,"automotive, car, dealership",Motion-Driven + 3D & Hyperrealism,"Dark Mode (OLED), Glassmorphism",Hero-Centric Design + Feature-Rich,Sales Intelligence Dashboard,Brand colors + Metallic accents + Dark/Light,Vehicle showcase. 360° views. Comparison tools. Financing calculator. Test drive booking. High-quality imagery.
55,Photography Studio,"photography, studio",Motion-Driven + Minimalism,"Aurora UI, Glassmorphism",Storytelling-Driven + Hero-Centric,N/A - Portfolio focused,Black + White + Minimal accent,Portfolio gallery. Before/after. Service packages. Booking system. Client galleries. Full-bleed imagery.
56,Coworking Space,"coworking, space",Vibrant & Block-based + Glassmorphism,"Minimalism, Motion-Driven",Hero-Centric Design + Feature-Rich,Occupancy Dashboard,Energetic colors + Wood tones + Brand accent,Space tour. Membership plans. Booking system. Amenities. Community events. Virtual tour.
57,Cleaning Service,"appointment, booking, cleaning, consultation, service",Soft UI Evolution + Flat Design,"Minimalism, Micro-interactions",Conversion-Optimized + Trust,Service Analytics,Fresh Blue (#00B4D8) + Clean White + Green,Service packages. Booking system. Price calculator. Before/after gallery. Reviews. Trust badges.
58,Home Services (Plumber/Electrician),"appointment, booking, consultation, electrician, home, plumber, service, services",Flat Design + Trust & Authority,"Minimalism, Accessible & Ethical",Conversion-Optimized + Trust,Service Analytics,Trust Blue + Safety Orange + Professional grey,Service list. Emergency contact. Booking. Price transparency. Certifications. Local trust signals.
59,Childcare/Daycare,"childcare, daycare",Claymorphism + Vibrant & Block-based,"Soft UI Evolution, Accessible & Ethical",Social Proof-Focused + Trust,Parent Dashboard,Playful pastels + Safe colors + Warm accents,Programs. Staff profiles. Safety certifications. Parent portal. Activity updates. Cheerful imagery.
60,Senior Care/Elderly,"care, elderly, senior",Accessible & Ethical + Soft UI Evolution,"Minimalism, Neumorphism",Trust & Authority + Social Proof,Healthcare Analytics,Calm Blue + Warm neutrals + Large text,Care services. Staff qualifications. Facility tour. Family portal. Large touch targets. High contrast. Accessibility-first.
61,Medical Clinic,"clinic, medical",Accessible & Ethical + Minimalism,"Neumorphism, Trust & Authority",Trust & Authority + Conversion,Healthcare Analytics,Medical Blue (#0077B6) + Trust White + Calm Green,Services. Doctor profiles. Online booking. Patient portal. Insurance info. HIPAA compliant. Trust signals.
62,Pharmacy/Drug Store,"drug, pharmacy, store",Flat Design + Accessible & Ethical,"Minimalism, Trust & Authority",Conversion-Optimized + Trust,Inventory Dashboard,Pharmacy Green + Trust Blue + Clean White,Product catalog. Prescription upload. Refill reminders. Health info. Store locator. Safety certifications.
63,Dental Practice,"dental, practice",Soft UI Evolution + Minimalism,"Accessible & Ethical, Trust & Authority",Social Proof-Focused + Conversion,Patient Analytics,Fresh Blue + White + Smile Yellow accent,Services. Dentist profiles. Before/after. Online booking. Insurance. Patient testimonials. Friendly imagery.
64,Veterinary Clinic,"clinic, veterinary",Claymorphism + Accessible & Ethical,"Soft UI Evolution, Flat Design",Social Proof-Focused + Trust,Pet Health Dashboard,Caring Blue + Pet-friendly colors + Warm accents,Pet services. Vet profiles. Online booking. Pet portal. Emergency info. Friendly animal imagery.
65,Florist/Plant Shop,"florist, plant, shop",Organic Biophilic + Vibrant & Block-based,"Aurora UI, Motion-Driven",Hero-Centric Design + Conversion,E-commerce Analytics,Natural Green + Floral pinks/purples + Earth tones,Product catalog. Occasion categories. Delivery scheduling. Care guides. Seasonal collections. Beautiful imagery.
66,Bakery/Cafe,"bakery, cafe",Vibrant & Block-based + Soft UI Evolution,"Claymorphism, Motion-Driven",Hero-Centric Design + Conversion,N/A - Order focused,Warm Brown + Cream + Appetizing accents,Menu display. Online ordering. Location/hours. Catering. Seasonal specials. Appetizing photography.
67,Coffee Shop,"coffee, shop",Minimalism + Organic Biophilic,"Soft UI Evolution, Flat Design",Hero-Centric Design + Conversion,N/A - Order focused,Coffee Brown (#6F4E37) + Cream + Warm accents,Menu. Online ordering. Loyalty program. Location. Story/origin. Cozy aesthetic.
68,Brewery/Winery,"brewery, winery",Motion-Driven + Storytelling-Driven,"Dark Mode (OLED), Organic Biophilic",Storytelling-Driven + Hero-Centric,N/A - E-commerce focused,Deep amber/burgundy + Gold + Craft aesthetic,Product showcase. Story/heritage. Tasting notes. Events. Club membership. Artisanal imagery.
69,Airline,"ai, airline, artificial-intelligence, automation, machine-learning, ml",Minimalism + Glassmorphism,"Motion-Driven, Accessible & Ethical",Conversion-Optimized + Feature-Rich,Operations Dashboard,Sky Blue + Brand colors + Trust accents,Flight search. Booking. Check-in. Boarding pass. Loyalty program. Route maps. Mobile-first.
70,News/Media Platform,"content, entertainment, media, news, platform, streaming, video",Minimalism + Flat Design,"Dark Mode (OLED), Accessible & Ethical",Hero-Centric Design + Feature-Rich,Media Analytics Dashboard,Brand colors + High contrast + Category colors,Article layout. Breaking news. Categories. Search. Subscription. Mobile reading. Fast loading.
71,Magazine/Blog,"articles, blog, content, magazine, posts, writing",Swiss Modernism 2.0 + Motion-Driven,"Minimalism, Aurora UI",Storytelling-Driven + Hero-Centric,Content Analytics,Editorial colors + Brand primary + Clean white,Article showcase. Category navigation. Author profiles. Newsletter signup. Related content. Typography-focused.
72,Freelancer Platform,"freelancer, platform",Flat Design + Minimalism,"Vibrant & Block-based, Micro-interactions",Feature-Rich Showcase + Conversion,Marketplace Analytics,Professional Blue + Success Green + Neutral,Profile creation. Portfolio. Skill matching. Messaging. Payment. Reviews. Project management.
73,Consulting Firm,"consulting, firm",Trust & Authority + Minimalism,"Swiss Modernism 2.0, Accessible & Ethical",Trust & Authority + Feature-Rich,N/A - Lead generation,Navy + Gold + Professional grey,Service areas. Case studies. Team profiles. Thought leadership. Contact. Professional credibility.
74,Marketing Agency,"agency, creative, design, marketing, studio",Brutalism + Motion-Driven,"Vibrant & Block-based, Aurora UI",Storytelling-Driven + Feature-Rich,Campaign Analytics,Bold brand colors + Creative freedom,Portfolio. Case studies. Services. Team. Creative showcase. Results-focused. Bold aesthetic.
75,Event Management,"conference, event, management, meetup, registration, ticket",Vibrant & Block-based + Motion-Driven,"Glassmorphism, Aurora UI",Hero-Centric Design + Feature-Rich,Event Analytics,Event theme colors + Excitement accents,Event showcase. Registration. Agenda. Speakers. Sponsors. Ticket sales. Countdown timer.
76,Conference/Webinar Platform,"conference, platform, webinar",Glassmorphism + Minimalism,"Motion-Driven, Flat Design",Feature-Rich Showcase + Conversion,Attendee Analytics,Professional Blue + Video accent + Brand,Registration. Agenda. Speaker profiles. Live stream. Networking. Recording access. Virtual event features.
77,Membership/Community,"community, membership",Vibrant & Block-based + Soft UI Evolution,"Bento Box Grid, Micro-interactions",Social Proof-Focused + Conversion,Community Analytics,Community brand colors + Engagement accents,Member benefits. Pricing tiers. Community showcase. Events. Member directory. Exclusive content.
78,Newsletter Platform,"newsletter, platform",Minimalism + Flat Design,"Swiss Modernism 2.0, Accessible & Ethical",Minimal & Direct + Conversion,Email Analytics,Brand primary + Clean white + CTA accent,Subscribe form. Archive. About. Social proof. Sample content. Simple conversion.
79,Digital Products/Downloads,"digital, downloads, products",Vibrant & Block-based + Motion-Driven,"Glassmorphism, Bento Box Grid",Feature-Rich Showcase + Conversion,E-commerce Analytics,Product category colors + Brand + Success green,Product showcase. Preview. Pricing. Instant delivery. License management. Customer reviews.
80,Church/Religious Organization,"church, organization, religious",Accessible & Ethical + Soft UI Evolution,"Minimalism, Trust & Authority",Hero-Centric Design + Social Proof,N/A - Community focused,Warm Gold + Deep Purple/Blue + White,Service times. Events. Sermons. Community. Giving. Location. Welcoming imagery.
81,Sports Team/Club,"club, sports, team",Vibrant & Block-based + Motion-Driven,"Dark Mode (OLED), 3D & Hyperrealism",Hero-Centric Design + Feature-Rich,Performance Analytics,Team colors + Energetic accents,Schedule. Roster. News. Tickets. Merchandise. Fan engagement. Action imagery.
82,Museum/Gallery,"gallery, museum",Minimalism + Motion-Driven,"Swiss Modernism 2.0, 3D & Hyperrealism",Storytelling-Driven + Feature-Rich,Visitor Analytics,Art-appropriate neutrals + Exhibition accents,Exhibitions. Collections. Tickets. Events. Virtual tours. Educational content. Art-focused design.
83,Theater/Cinema,"cinema, theater",Dark Mode (OLED) + Motion-Driven,"Vibrant & Block-based, Glassmorphism",Hero-Centric Design + Conversion,Booking Analytics,Dark + Spotlight accents + Gold,Showtimes. Seat selection. Trailers. Coming soon. Membership. Dramatic imagery.
84,Language Learning App,"app, language, learning",Claymorphism + Vibrant & Block-based,"Micro-interactions, Flat Design",Feature-Rich Showcase + Social Proof,Learning Analytics,Playful colors + Progress indicators + Country flags,Lesson structure. Progress tracking. Gamification. Speaking practice. Community. Achievement badges.
85,Coding Bootcamp,"bootcamp, coding",Dark Mode (OLED) + Minimalism,"Cyberpunk UI, Flat Design",Feature-Rich Showcase + Social Proof,Student Analytics,Code editor colors + Brand + Success green,Curriculum. Projects. Career outcomes. Alumni. Pricing. Application. Terminal aesthetic.
86,Cybersecurity Platform,"cyber, security, platform",Cyberpunk UI + Dark Mode (OLED),"Neubrutalism, Minimal & Direct",Trust & Authority + Real-Time,Real-Time Monitoring + Heat Map,Matrix Green + Deep Black + Terminal feel,Data density. Threat visualization. Dark mode default.
87,Developer Tool / IDE,"dev, developer, tool, ide",Dark Mode (OLED) + Minimalism,"Flat Design, Bento Box Grid",Minimal & Direct + Documentation,Real-Time Monitor + Terminal,Dark syntax theme colors + Blue focus,Keyboard shortcuts. Syntax highlighting. Fast performance.
88,Biotech / Life Sciences,"biotech, biology, science",Glassmorphism + Clean Science,"Minimalism, Organic Biophilic",Storytelling-Driven + Research,Data-Dense + Predictive,Sterile White + DNA Blue + Life Green,Data accuracy. Cleanliness. Complex data viz.
89,Space Tech / Aerospace,"aerospace, space, tech",Holographic / HUD + Dark Mode,"Glassmorphism, 3D & Hyperrealism",Immersive Experience + Hero,Real-Time Monitoring + 3D,Deep Space Black + Star White + Metallic,High-tech feel. Precision. Telemetry data.
90,Architecture / Interior,"architecture, design, interior",Exaggerated Minimalism + High Imagery,"Swiss Modernism 2.0, Parallax",Portfolio Grid + Visuals,Project Management + Gallery,Monochrome + Gold Accent + High Imagery,High-res images. Typography. Space.
91,Quantum Computing Interface,"quantum, computing, physics, qubit, future, science",Holographic / HUD + Dark Mode,"Glassmorphism, Spatial UI",Immersive/Interactive Experience,3D Spatial Data + Real-Time Monitor,Quantum Blue #00FFFF + Deep Black + Interference patterns,Visualize complexity. Qubit states. Probability clouds. High-tech trust.
92,Biohacking / Longevity App,"biohacking, health, longevity, tracking, wellness, science",Biomimetic / Organic 2.0,"Minimalism, Dark Mode (OLED)",Data-Dense + Storytelling,Real-Time Monitor + Biological Data,Cellular Pink/Red + DNA Blue + Clean White,Personal data privacy. Scientific credibility. Biological visualizations.
93,Autonomous Drone Fleet Manager,"drone, autonomous, fleet, aerial, logistics, robotics",HUD / Sci-Fi FUI,"Real-Time Monitor, Spatial UI",Real-Time Monitor,Geographic + Real-Time,Tactical Green #00FF00 + Alert Red + Map Dark,Real-time telemetry. 3D spatial awareness. Latency indicators. Safety alerts.
94,Generative Art Platform,"art, generative, ai, creative, platform, gallery",Minimalism (Frame) + Gen Z Chaos,"Masonry Grid, Dark Mode",Bento Grid Showcase,Gallery / Portfolio,Neutral #F5F5F5 (Canvas) + User Content,Content is king. Fast loading. Creator attribution. Minting flow.
95,Spatial Computing OS / App,"spatial, vr, ar, vision, os, immersive, mixed-reality",Spatial UI (VisionOS),"Glassmorphism, 3D & Hyperrealism",Immersive/Interactive Experience,Spatial Dashboard,Frosted Glass + System Colors + Depth,Gaze/Pinch interaction. Depth hierarchy. Environment awareness.
96,Sustainable Energy / Climate Tech,"climate, energy, sustainable, green, tech, carbon",Organic Biophilic + E-Ink / Paper,"Data-Dense, Swiss Modernism",Interactive Demo + Data,Energy/Utilities Dashboard,Earth Green + Sky Blue + Solar Yellow,Data transparency. Impact visualization. Low-carbon web design.
.claude/skills/ui-ux-pro-max/data/react-performance.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity
1,Async Waterfall,Defer Await,async await defer branch,React/Next.js,Move await into branches where actually used to avoid blocking unused code paths,Move await operations into branches where they're needed,Await at top of function blocking all branches,"if (skip) return { skipped: true }; const data = await fetch()","const data = await fetch(); if (skip) return { skipped: true }",Critical
2,Async Waterfall,Promise.all Parallel,promise all parallel concurrent,React/Next.js,Execute independent async operations concurrently using Promise.all(),Use Promise.all() for independent operations,Sequential await for independent operations,"const [user, posts] = await Promise.all([fetchUser(), fetchPosts()])","const user = await fetchUser(); const posts = await fetchPosts()",Critical
3,Async Waterfall,Dependency Parallelization,better-all dependency parallel,React/Next.js,Use better-all for operations with partial dependencies to maximize parallelism,Use better-all to start each task at earliest possible moment,Wait for unrelated data before starting dependent fetch,"await all({ user() {}, config() {}, profile() { return fetch((await this.$.user).id) } })","const [user, config] = await Promise.all([...]); const profile = await fetchProfile(user.id)",Critical
4,Async Waterfall,API Route Optimization,api route waterfall promise,React/Next.js,In API routes start independent operations immediately even if not awaited yet,Start promises early and await late,Sequential awaits in API handlers,"const sessionP = auth(); const configP = fetchConfig(); const session = await sessionP","const session = await auth(); const config = await fetchConfig()",Critical
5,Async Waterfall,Suspense Boundaries,suspense streaming boundary,React/Next.js,Use Suspense to show wrapper UI faster while data loads,Wrap async components in Suspense boundaries,Await data blocking entire page render,"<Suspense fallback={<Skeleton />}><DataDisplay /></Suspense>","const data = await fetchData(); return <DataDisplay data={data} />",High
6,Bundle Size,Barrel Imports,barrel import direct path,React/Next.js,Import directly from source files instead of barrel files to avoid loading unused modules,Import directly from source path,Import from barrel/index files,"import Check from 'lucide-react/dist/esm/icons/check'","import { Check } from 'lucide-react'",Critical
7,Bundle Size,Dynamic Imports,dynamic import lazy next,React/Next.js,Use next/dynamic to lazy-load large components not needed on initial render,Use dynamic() for heavy components,Import heavy components at top level,"const Monaco = dynamic(() => import('./monaco'), { ssr: false })","import { MonacoEditor } from './monaco-editor'",Critical
8,Bundle Size,Defer Third Party,analytics defer third-party,React/Next.js,Load analytics and logging after hydration since they don't block interaction,Load non-critical scripts after hydration,Include analytics in main bundle,"const Analytics = dynamic(() => import('@vercel/analytics'), { ssr: false })","import { Analytics } from '@vercel/analytics/react'",Medium
9,Bundle Size,Conditional Loading,conditional module lazy,React/Next.js,Load large data or modules only when a feature is activated,Dynamic import when feature enabled,Import large modules unconditionally,"useEffect(() => { if (enabled) import('./heavy.js') }, [enabled])","import { heavyData } from './heavy.js'",High
10,Bundle Size,Preload Intent,preload hover focus intent,React/Next.js,Preload heavy bundles on hover/focus before they're needed,Preload on user intent signals,Load only on click,"onMouseEnter={() => import('./editor')}","onClick={() => import('./editor')}",Medium
11,Server,React.cache Dedup,react cache deduplicate request,React/Next.js,Use React.cache() for server-side request deduplication within single request,Wrap data fetchers with cache(),Fetch same data multiple times in tree,"export const getUser = cache(async () => await db.user.find())","export async function getUser() { return await db.user.find() }",Medium
12,Server,LRU Cache Cross-Request,lru cache cross request,React/Next.js,Use LRU cache for data shared across sequential requests,Use LRU for cross-request caching,Refetch same data on every request,"const cache = new LRUCache({ max: 1000, ttl: 5*60*1000 })","Always fetch from database",High
13,Server,Minimize Serialization,serialization rsc boundary,React/Next.js,Only pass fields that client actually uses across RSC boundaries,Pass only needed fields to client components,Pass entire objects to client,"<Profile name={user.name} />","<Profile user={user} /> // 50 fields serialized",High
14,Server,Parallel Fetching,parallel fetch component composition,React/Next.js,Restructure components to parallelize data fetching in RSC,Use component composition for parallel fetches,Sequential fetches in parent component,"<Header /><Sidebar /> // both fetch in parallel","const header = await fetchHeader(); return <><div>{header}</div><Sidebar /></>",Critical
15,Server,After Non-blocking,after non-blocking logging,React/Next.js,Use Next.js after() to schedule work after response is sent,Use after() for logging/analytics,Block response for non-critical operations,"after(async () => { await logAction() }); return Response.json(data)","await logAction(); return Response.json(data)",Medium
16,Client,SWR Deduplication,swr dedup cache revalidate,React/Next.js,Use SWR for automatic request deduplication and caching,Use useSWR for client data fetching,Manual fetch in useEffect,"const { data } = useSWR('/api/users', fetcher)","useEffect(() => { fetch('/api/users').then(setUsers) }, [])",Medium-High
17,Client,Event Listener Dedup,event listener deduplicate global,React/Next.js,Share global event listeners across component instances,Use useSWRSubscription for shared listeners,Register listener per component instance,"useSWRSubscription('global-keydown', () => { window.addEventListener... })","useEffect(() => { window.addEventListener('keydown', handler) }, [])",Low
18,Rerender,Defer State Reads,state read callback subscription,React/Next.js,Don't subscribe to state only used in callbacks,Read state on-demand in callbacks,Subscribe to state used only in handlers,"const handleClick = () => { const params = new URLSearchParams(location.search) }","const params = useSearchParams(); const handleClick = () => { params.get('ref') }",Medium
19,Rerender,Memoized Components,memo extract expensive,React/Next.js,Extract expensive work into memoized components for early returns,Extract to memo() components,Compute expensive values before early return,"const UserAvatar = memo(({ user }) => ...); if (loading) return <Skeleton />","const avatar = useMemo(() => compute(user)); if (loading) return <Skeleton />",Medium
20,Rerender,Narrow Dependencies,effect dependency primitive,React/Next.js,Specify primitive dependencies instead of objects in effects,Use primitive values in dependency arrays,Use object references as dependencies,"useEffect(() => { console.log(user.id) }, [user.id])","useEffect(() => { console.log(user.id) }, [user])",Low
21,Rerender,Derived State,derived boolean subscription,React/Next.js,Subscribe to derived booleans instead of continuous values,Use derived boolean state,Subscribe to continuous values,"const isMobile = useMediaQuery('(max-width: 767px)')","const width = useWindowWidth(); const isMobile = width < 768",Medium
22,Rerender,Functional setState,functional setstate callback,React/Next.js,Use functional setState updates for stable callbacks and no stale closures,Use functional form: setState(curr => ...),Reference state directly in setState,"setItems(curr => [...curr, newItem])","setItems([...items, newItem]) // items in deps",Medium
23,Rerender,Lazy State Init,usestate lazy initialization,React/Next.js,Pass function to useState for expensive initial values,Use function form for expensive init,Compute expensive value directly,"useState(() => buildSearchIndex(items))","useState(buildSearchIndex(items)) // runs every render",Medium
24,Rerender,Transitions,starttransition non-urgent,React/Next.js,Mark frequent non-urgent state updates as transitions,Use startTransition for non-urgent updates,Block UI on every state change,"startTransition(() => setScrollY(window.scrollY))","setScrollY(window.scrollY) // blocks on every scroll",Medium
25,Rendering,SVG Animation Wrapper,svg animation wrapper div,React/Next.js,Wrap SVG in div and animate wrapper for hardware acceleration,Animate div wrapper around SVG,Animate SVG element directly,"<div class='animate-spin'><svg>...</svg></div>","<svg class='animate-spin'>...</svg>",Low
26,Rendering,Content Visibility,content-visibility auto,React/Next.js,Apply content-visibility: auto to defer off-screen rendering,Use content-visibility for long lists,Render all list items immediately,".item { content-visibility: auto; contain-intrinsic-size: 0 80px }","Render 1000 items without optimization",High
27,Rendering,Hoist Static JSX,hoist static jsx element,React/Next.js,Extract static JSX outside components to avoid re-creation,Hoist static elements to module scope,Create static elements inside components,"const skeleton = <div class='animate-pulse' />; function C() { return skeleton }","function C() { return <div class='animate-pulse' /> }",Low
28,Rendering,Hydration No Flicker,hydration mismatch flicker,React/Next.js,Use inline script to set client-only data before hydration,Inject sync script for client-only values,Use useEffect causing flash,"<script dangerouslySetInnerHTML={{ __html: 'el.className = localStorage.theme' }} />","useEffect(() => setTheme(localStorage.theme), []) // flickers",Medium
29,Rendering,Conditional Render,conditional render ternary,React/Next.js,Use ternary instead of && when condition can be 0 or NaN,Use explicit ternary for conditionals,Use && with potentially falsy numbers,"{count > 0 ? <Badge>{count}</Badge> : null}","{count && <Badge>{count}</Badge>} // renders '0'",Low
30,Rendering,Activity Component,activity show hide preserve,React/Next.js,Use Activity component to preserve state/DOM for toggled components,Use Activity for expensive toggle components,Unmount/remount on visibility toggle,"<Activity mode={isOpen ? 'visible' : 'hidden'}><Menu /></Activity>","{isOpen && <Menu />} // loses state",Medium
31,JS Perf,Batch DOM CSS,batch dom css reflow,React/Next.js,Group CSS changes via classes or cssText to minimize reflows,Use class toggle or cssText,Change styles one property at a time,"element.classList.add('highlighted')","el.style.width='100px'; el.style.height='200px'",Medium
32,JS Perf,Index Map Lookup,map index lookup find,React/Next.js,Build Map for repeated lookups instead of multiple .find() calls,Build index Map for O(1) lookups,Use .find() in loops,"const byId = new Map(users.map(u => [u.id, u])); byId.get(id)","users.find(u => u.id === order.userId) // O(n) each time",Low-Medium
33,JS Perf,Cache Property Access,cache property loop,React/Next.js,Cache object property lookups in hot paths,Cache values before loops,Access nested properties in loops,"const val = obj.config.settings.value; for (...) process(val)","for (...) process(obj.config.settings.value)",Low-Medium
34,JS Perf,Cache Function Results,memoize cache function,React/Next.js,Use module-level Map to cache repeated function results,Use Map cache for repeated calls,Recompute same values repeatedly,"const cache = new Map(); if (cache.has(x)) return cache.get(x)","slugify(name) // called 100 times same input",Medium
35,JS Perf,Cache Storage API,localstorage cache read,React/Next.js,Cache localStorage/sessionStorage reads in memory,Cache storage reads in Map,Read storage on every call,"if (!cache.has(key)) cache.set(key, localStorage.getItem(key))","localStorage.getItem('theme') // every call",Low-Medium
36,JS Perf,Combine Iterations,combine filter map loop,React/Next.js,Combine multiple filter/map into single loop,Single loop for multiple categorizations,Chain multiple filter() calls,"for (u of users) { if (u.isAdmin) admins.push(u); if (u.isTester) testers.push(u) }","users.filter(admin); users.filter(tester); users.filter(inactive)",Low-Medium
37,JS Perf,Length Check First,length check array compare,React/Next.js,Check array lengths before expensive comparisons,Early return if lengths differ,Always run expensive comparison,"if (a.length !== b.length) return true; // then compare","a.sort().join() !== b.sort().join() // even when lengths differ",Medium-High
38,JS Perf,Early Return,early return exit function,React/Next.js,Return early when result is determined to skip processing,Return immediately on first error,Process all items then check errors,"for (u of users) { if (!u.email) return { error: 'Email required' } }","let hasError; for (...) { if (!email) hasError=true }; if (hasError)...",Low-Medium
39,JS Perf,Hoist RegExp,regexp hoist module,React/Next.js,Don't create RegExp inside render - hoist or memoize,Hoist RegExp to module scope,Create RegExp every render,"const EMAIL_RE = /^[^@]+@[^@]+$/; function validate() { EMAIL_RE.test(x) }","function C() { const re = new RegExp(pattern); re.test(x) }",Low-Medium
40,JS Perf,Loop Min Max,loop min max sort,React/Next.js,Use loop for min/max instead of sort - O(n) vs O(n log n),Single pass loop for min/max,Sort array to find min/max,"let max = arr[0]; for (x of arr) if (x > max) max = x","arr.sort((a,b) => b-a)[0] // O(n log n)",Low
41,JS Perf,Set Map Lookups,set map includes has,React/Next.js,Use Set/Map for O(1) lookups instead of array.includes(),Convert to Set for membership checks,Use .includes() for repeated checks,"const allowed = new Set(['a','b']); allowed.has(id)","const allowed = ['a','b']; allowed.includes(id)",Low-Medium
42,JS Perf,toSorted Immutable,tosorted sort immutable,React/Next.js,Use toSorted() instead of sort() to avoid mutating arrays,Use toSorted() for immutability,Mutate arrays with sort(),"users.toSorted((a,b) => a.name.localeCompare(b.name))","users.sort((a,b) => a.name.localeCompare(b.name)) // mutates",Medium-High
43,Advanced,Event Handler Refs,useeffectevent ref handler,React/Next.js,Store callbacks in refs for stable effect subscriptions,Use useEffectEvent for stable handlers,Re-subscribe on every callback change,"const onEvent = useEffectEvent(handler); useEffect(() => { listen(onEvent) }, [])","useEffect(() => { listen(handler) }, [handler]) // re-subscribes",Low
44,Advanced,useLatest Hook,uselatest ref callback,React/Next.js,Access latest values in callbacks without adding to dependency arrays,Use useLatest for fresh values in stable callbacks,Add callback to effect dependencies,"const cbRef = useLatest(cb); useEffect(() => { setTimeout(() => cbRef.current()) }, [])","useEffect(() => { setTimeout(() => cb()) }, [cb]) // re-runs",Low
.claude/skills/ui-ux-pro-max/data/stacks/astro.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Architecture,Use Islands Architecture,Astro's partial hydration only loads JS for interactive components,Interactive components with client directives,Hydrate entire page like traditional SPA,<Counter client:load />,Everything as client component,High,https://docs.astro.build/en/concepts/islands/
2,Architecture,Default to zero JS,Astro ships zero JS by default - add only when needed,Static components without client directive,Add client:load to everything,<Header /> (static),<Header client:load /> (unnecessary),High,https://docs.astro.build/en/basics/astro-components/
3,Architecture,Choose right client directive,Different directives for different hydration timing,client:visible for below-fold client:idle for non-critical,client:load for everything,<Comments client:visible />,<Comments client:load />,Medium,https://docs.astro.build/en/reference/directives-reference/#client-directives
4,Architecture,Use content collections,Type-safe content management for blogs docs,Content collections for structured content,Loose markdown files without schema,const posts = await getCollection('blog'),import.meta.glob('./posts/*.md'),High,https://docs.astro.build/en/guides/content-collections/
5,Architecture,Define collection schemas,Zod schemas for content validation,Schema with required fields and types,No schema validation,"defineCollection({ schema: z.object({...}) })",defineCollection({}),High,https://docs.astro.build/en/guides/content-collections/#defining-a-collection-schema
6,Routing,Use file-based routing,Create routes by adding .astro files in pages/,pages/ directory for routes,Manual route configuration,src/pages/about.astro,Custom router setup,Medium,https://docs.astro.build/en/basics/astro-pages/
7,Routing,Dynamic routes with brackets,Use [param] for dynamic routes,Bracket notation for params,Query strings for dynamic content,pages/blog/[slug].astro,pages/blog.astro?slug=x,Medium,https://docs.astro.build/en/guides/routing/#dynamic-routes
8,Routing,Use getStaticPaths for SSG,Generate static pages at build time,getStaticPaths for known dynamic routes,Fetch at runtime for static content,"export async function getStaticPaths() { return [...] }",No getStaticPaths with dynamic route,High,https://docs.astro.build/en/reference/api-reference/#getstaticpaths
9,Routing,Enable SSR when needed,Server-side rendering for dynamic content,output: 'server' or 'hybrid' for dynamic,SSR for purely static sites,"export const prerender = false;",SSR for static blog,Medium,https://docs.astro.build/en/guides/server-side-rendering/
10,Components,Keep .astro for static,Use .astro components for static content,Astro components for layout structure,React/Vue for static markup,<Layout><slot /></Layout>,<ReactLayout>{children}</ReactLayout>,High,
11,Components,Use framework components for interactivity,React Vue Svelte for complex interactivity,Framework component with client directive,Astro component with inline scripts,<ReactCounter client:load />,<script> in .astro for complex state,Medium,https://docs.astro.build/en/guides/framework-components/
12,Components,Pass data via props,Astro components receive props in frontmatter,Astro.props for component data,Global state for simple data,"const { title } = Astro.props;",Import global store,Low,https://docs.astro.build/en/basics/astro-components/#component-props
13,Components,Use slots for composition,Named and default slots for flexible layouts,<slot /> for child content,Props for HTML content,<slot name="header" />,<Component header={<div>...</div>} />,Medium,https://docs.astro.build/en/basics/astro-components/#slots
14,Components,Colocate component styles,Scoped styles in component file,<style> in same .astro file,Separate CSS files for component styles,<style> .card { } </style>,import './Card.css',Low,
15,Styling,Use scoped styles by default,Astro scopes styles to component automatically,<style> for component-specific styles,Global styles for everything,<style> h1 { } </style> (scoped),<style is:global> for everything,Medium,https://docs.astro.build/en/guides/styling/#scoped-styles
16,Styling,Use is:global sparingly,Global styles only when truly needed,is:global for base styles or overrides,is:global for component styles,<style is:global> body { } </style>,<style is:global> .card { } </style>,Medium,
17,Styling,Integrate Tailwind properly,Use @astrojs/tailwind integration,Official Tailwind integration,Manual Tailwind setup,npx astro add tailwind,Manual PostCSS config,Low,https://docs.astro.build/en/guides/integrations-guide/tailwind/
18,Styling,Use CSS variables for theming,Define tokens in :root,CSS custom properties for themes,Hardcoded colors everywhere,:root { --primary: #3b82f6; },color: #3b82f6; everywhere,Medium,
19,Data,Fetch in frontmatter,Data fetching in component frontmatter,Top-level await in frontmatter,useEffect for initial data,const data = await fetch(url),client-side fetch on mount,High,https://docs.astro.build/en/guides/data-fetching/
20,Data,Use Astro.glob for local files,Import multiple local files,Astro.glob for markdown/data files,Manual imports for each file,const posts = await Astro.glob('./posts/*.md'),"import post1; import post2;",Medium,
21,Data,Prefer content collections over glob,Type-safe collections for structured content,getCollection() for blog/docs,Astro.glob for structured content,await getCollection('blog'),await Astro.glob('./blog/*.md'),High,
22,Data,Use environment variables correctly,Import.meta.env for env vars,PUBLIC_ prefix for client vars,Expose secrets to client,import.meta.env.PUBLIC_API_URL,import.meta.env.SECRET in client,High,https://docs.astro.build/en/guides/environment-variables/
23,Performance,Preload critical assets,Use link preload for important resources,Preload fonts above-fold images,No preload hints,"<link rel=""preload"" href=""font.woff2"" as=""font"">",No preload for critical assets,Medium,
24,Performance,Optimize images with astro:assets,Built-in image optimization,<Image /> component for optimization,<img> for local images,"import { Image } from 'astro:assets';","<img src=""./image.jpg"">",High,https://docs.astro.build/en/guides/images/
25,Performance,Use picture for responsive images,Multiple formats and sizes,<Picture /> for art direction,Single image size for all screens,<Picture /> with multiple sources,<Image /> with single size,Medium,
26,Performance,Lazy load below-fold content,Defer loading non-critical content,loading=lazy for images client:visible for components,Load everything immediately,"<img loading=""lazy"">",No lazy loading,Medium,
27,Performance,Minimize client directives,Each directive adds JS bundle,Audit client: usage regularly,Sprinkle client:load everywhere,Only interactive components hydrated,Every component with client:load,High,
28,ViewTransitions,Enable View Transitions,Smooth page transitions,<ViewTransitions /> in head,Full page reloads,"import { ViewTransitions } from 'astro:transitions';",No transition API,Medium,https://docs.astro.build/en/guides/view-transitions/
29,ViewTransitions,Use transition:name,Named elements for morphing,transition:name for persistent elements,Unnamed transitions,"<header transition:name=""header"">",<header> without name,Low,
30,ViewTransitions,Handle transition:persist,Keep state across navigations,transition:persist for media players,Re-initialize on every navigation,"<video transition:persist id=""player"">",Video restarts on navigation,Medium,
31,ViewTransitions,Add fallback for no-JS,Graceful degradation,Content works without JS,Require JS for basic navigation,Static content accessible,Broken without ViewTransitions JS,High,
32,SEO,Use built-in SEO component,Head management for meta tags,Astro SEO integration or manual head,No meta tags,"<title>{title}</title><meta name=""description"">",No SEO tags,High,
33,SEO,Generate sitemap,Automatic sitemap generation,@astrojs/sitemap integration,Manual sitemap maintenance,npx astro add sitemap,Hand-written sitemap.xml,Medium,https://docs.astro.build/en/guides/integrations-guide/sitemap/
34,SEO,Add RSS feed for content,RSS for blogs and content sites,@astrojs/rss for feed generation,No RSS feed,rss() helper in pages/rss.xml.js,No feed for blog,Low,https://docs.astro.build/en/guides/rss/
35,SEO,Use canonical URLs,Prevent duplicate content issues,Astro.url for canonical generation,"<link rel=""canonical"" href={Astro.url}>",No canonical tags,Medium,
36,Integrations,Use official integrations,Astro's integration system,npx astro add for integrations,Manual configuration,npx astro add react,Manual React setup,Medium,https://docs.astro.build/en/guides/integrations-guide/
37,Integrations,Configure integrations in astro.config,Centralized configuration,integrations array in config,Scattered configuration,"integrations: [react(), tailwind()]",Multiple config files,Low,
38,Integrations,Use adapter for deployment,Platform-specific adapters,Correct adapter for host,Wrong or no adapter,@astrojs/vercel for Vercel,No adapter for SSR,High,https://docs.astro.build/en/guides/deploy/
39,TypeScript,Enable TypeScript,Type safety for Astro projects,tsconfig.json with astro types,No TypeScript,Astro TypeScript template,JavaScript only,Medium,https://docs.astro.build/en/guides/typescript/
40,TypeScript,Type component props,Define prop interfaces,Props interface in frontmatter,Untyped props,"interface Props { title: string }",No props typing,Medium,
41,TypeScript,Use strict mode,Catch errors early,strict: true in tsconfig,Loose TypeScript config,strictest template,base template,Low,
42,Markdown,Use MDX for components,Components in markdown content,@astrojs/mdx for interactive docs,Plain markdown with workarounds,<Component /> in .mdx,HTML in .md files,Medium,https://docs.astro.build/en/guides/integrations-guide/mdx/
43,Markdown,Configure markdown plugins,Extend markdown capabilities,remarkPlugins rehypePlugins in config,Manual HTML for features,remarkPlugins: [remarkToc],Manual TOC in every post,Low,
44,Markdown,Use frontmatter for metadata,Structured post metadata,Frontmatter with typed schema,Inline metadata,title date in frontmatter,# Title as first line,Medium,
45,API,Use API routes for endpoints,Server endpoints in pages/api,pages/api/[endpoint].ts for APIs,External API for simple endpoints,pages/api/posts.json.ts,Separate Express server,Medium,https://docs.astro.build/en/guides/endpoints/
46,API,Return proper responses,Use Response object,new Response() with headers,Plain objects,return new Response(JSON.stringify(data)),return data,Medium,
47,API,Handle methods correctly,Export named method handlers,export GET POST handlers,Single default export,export const GET = async () => {},export default async () => {},Low,
48,Security,Sanitize user content,Prevent XSS in dynamic content,set:html only for trusted content,set:html with user input,"<Fragment set:html={sanitized} />","<div set:html={userInput} />",High,
49,Security,Use HTTPS in production,Secure connections,HTTPS for all production sites,HTTP in production,https://example.com,http://example.com,High,
50,Security,Validate API input,Check and sanitize all input,Zod validation for API routes,Trust all input,const body = schema.parse(data),const body = await request.json(),High,
51,Build,Use hybrid rendering,Mix static and dynamic pages,output: 'hybrid' for flexibility,All SSR or all static,prerender per-page basis,Single rendering mode,Medium,https://docs.astro.build/en/guides/server-side-rendering/#hybrid-rendering
52,Build,Analyze bundle size,Monitor JS bundle impact,Build output shows bundle sizes,Ignore bundle growth,Check astro build output,No size monitoring,Medium,
53,Build,Use prefetch,Preload linked pages,prefetch integration,No prefetch for navigation,npx astro add prefetch,Manual prefetch,Low,https://docs.astro.build/en/guides/prefetch/
.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Widgets,Use StatelessWidget when possible,Immutable widgets are simpler,StatelessWidget for static UI,StatefulWidget for everything,class MyWidget extends StatelessWidget,class MyWidget extends StatefulWidget (static),Medium,https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html
2,Widgets,Keep widgets small,Single responsibility principle,Extract widgets into smaller pieces,Large build methods,Column(children: [Header() Content()]),500+ line build method,Medium,
3,Widgets,Use const constructors,Compile-time constants for performance,const MyWidget() when possible,Non-const for static widgets,const Text('Hello'),Text('Hello') for literals,High,https://dart.dev/guides/language/language-tour#constant-constructors
4,Widgets,Prefer composition over inheritance,Combine widgets using children,Compose widgets,Extend widget classes,Container(child: MyContent()),class MyContainer extends Container,Medium,
5,State,Use setState correctly,Minimal state in StatefulWidget,setState for UI state changes,setState for business logic,setState(() { _counter++; }),Complex logic in setState,Medium,https://api.flutter.dev/flutter/widgets/State/setState.html
6,State,Avoid setState in build,Never call setState during build,setState in callbacks only,setState in build method,onPressed: () => setState(() {}),build() { setState(); },High,
7,State,Use state management for complex apps,Provider Riverpod BLoC,State management for shared state,setState for global state,Provider.of<MyState>(context),Global setState calls,Medium,
8,State,Prefer Riverpod or Provider,Recommended state solutions,Riverpod for new projects,InheritedWidget manually,ref.watch(myProvider),Custom InheritedWidget,Medium,https://riverpod.dev/
9,State,Dispose resources,Clean up controllers and subscriptions,dispose() for cleanup,Memory leaks from subscriptions,@override void dispose() { controller.dispose(); },No dispose implementation,High,
10,Layout,Use Column and Row,Basic layout widgets,Column Row for linear layouts,Stack for simple layouts,"Column(children: [Text(), Button()])",Stack for vertical list,Medium,https://api.flutter.dev/flutter/widgets/Column-class.html
11,Layout,Use Expanded and Flexible,Control flex behavior,Expanded to fill space,Fixed sizes in flex containers,Expanded(child: Container()),Container(width: 200) in Row,Medium,
12,Layout,Use SizedBox for spacing,Consistent spacing,SizedBox for gaps,Container for spacing only,SizedBox(height: 16),Container(height: 16),Low,
13,Layout,Use LayoutBuilder for responsive,Respond to constraints,LayoutBuilder for adaptive layouts,Fixed sizes for responsive,LayoutBuilder(builder: (context constraints) {}),Container(width: 375),Medium,https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html
14,Layout,Avoid deep nesting,Keep widget tree shallow,Extract deeply nested widgets,10+ levels of nesting,Extract widget to method or class,Column(Row(Column(Row(...)))),Medium,
15,Lists,Use ListView.builder,Lazy list building,ListView.builder for long lists,ListView with children for large lists,"ListView.builder(itemCount: 100, itemBuilder: ...)",ListView(children: items.map(...).toList()),High,https://api.flutter.dev/flutter/widgets/ListView-class.html
16,Lists,Provide itemExtent when known,Skip measurement,itemExtent for fixed height items,No itemExtent for uniform lists,ListView.builder(itemExtent: 50),ListView.builder without itemExtent,Medium,
17,Lists,Use keys for stateful items,Preserve widget state,Key for stateful list items,No key for dynamic lists,ListTile(key: ValueKey(item.id)),ListTile without key,High,
18,Lists,Use SliverList for custom scroll,Custom scroll effects,CustomScrollView with Slivers,Nested ListViews,CustomScrollView(slivers: [SliverList()]),ListView inside ListView,Medium,https://api.flutter.dev/flutter/widgets/SliverList-class.html
19,Navigation,Use Navigator 2.0 or GoRouter,Declarative routing,go_router for navigation,Navigator.push for complex apps,GoRouter(routes: [...]),Navigator.push everywhere,Medium,https://pub.dev/packages/go_router
20,Navigation,Use named routes,Organized navigation,Named routes for clarity,Anonymous routes,Navigator.pushNamed(context '/home'),Navigator.push(context MaterialPageRoute()),Low,
21,Navigation,Handle back button (PopScope),Android back behavior and predictive back (Android 14+),Use PopScope widget (WillPopScope is deprecated),Use WillPopScope,"PopScope(canPop: false, onPopInvoked: (didPop) => ...)",WillPopScope(onWillPop: ...),High,https://api.flutter.dev/flutter/widgets/PopScope-class.html
22,Navigation,Pass typed arguments,Type-safe route arguments,Typed route arguments,Dynamic arguments,MyRoute(id: '123'),arguments: {'id': '123'},Medium,
23,Async,Use FutureBuilder,Async UI building,FutureBuilder for async data,setState for async,FutureBuilder(future: fetchData()),fetchData().then((d) => setState()),Medium,https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
24,Async,Use StreamBuilder,Stream UI building,StreamBuilder for streams,Manual stream subscription,StreamBuilder(stream: myStream),stream.listen in initState,Medium,https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
25,Async,Handle loading and error states,Complete async UI states,ConnectionState checks,Only success state,if (snapshot.connectionState == ConnectionState.waiting),No loading indicator,High,
26,Async,Cancel subscriptions,Clean up stream subscriptions,Cancel in dispose,Memory leaks,subscription.cancel() in dispose,No subscription cleanup,High,
27,Theming,Use ThemeData,Consistent theming,ThemeData for app theme,Hardcoded colors,Theme.of(context).primaryColor,Color(0xFF123456) everywhere,Medium,https://api.flutter.dev/flutter/material/ThemeData-class.html
28,Theming,Use ColorScheme,Material 3 color system,ColorScheme for colors,Individual color properties,colorScheme: ColorScheme.fromSeed(),primaryColor: Colors.blue,Medium,
29,Theming,Access theme via context,Dynamic theme access,Theme.of(context),Static theme reference,Theme.of(context).textTheme.bodyLarge,TextStyle(fontSize: 16),Medium,
30,Theming,Support dark mode,Respect system theme,darkTheme in MaterialApp,Light theme only,"MaterialApp(theme: light, darkTheme: dark)",MaterialApp(theme: light),Medium,
31,Animation,Use implicit animations,Simple animations,AnimatedContainer AnimatedOpacity,Explicit for simple transitions,AnimatedContainer(duration: Duration()),AnimationController for fade,Low,https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html
32,Animation,Use AnimationController for complex,Fine-grained control,AnimationController with Ticker,Implicit for complex sequences,AnimationController(vsync: this),AnimatedContainer for staggered,Medium,
33,Animation,Dispose AnimationControllers,Clean up animation resources,dispose() for controllers,Memory leaks,controller.dispose() in dispose,No controller disposal,High,
34,Animation,Use Hero for transitions,Shared element transitions,Hero for navigation animations,Manual shared element,Hero(tag: 'image' child: Image()),Custom shared element animation,Low,https://api.flutter.dev/flutter/widgets/Hero-class.html
35,Forms,Use Form widget,Form validation,Form with GlobalKey,Individual validation,Form(key: _formKey child: ...),TextField without Form,Medium,https://api.flutter.dev/flutter/widgets/Form-class.html
36,Forms,Use TextEditingController,Control text input,Controller for text fields,onChanged for all text,final controller = TextEditingController(),onChanged: (v) => setState(),Medium,
37,Forms,Validate on submit,Form validation flow,_formKey.currentState!.validate(),Skip validation,if (_formKey.currentState!.validate()),Submit without validation,High,
38,Forms,Dispose controllers,Clean up text controllers,dispose() for controllers,Memory leaks,controller.dispose() in dispose,No controller disposal,High,
39,Performance,Use const widgets,Reduce rebuilds,const for static widgets,No const for literals,const Icon(Icons.add),Icon(Icons.add),High,
40,Performance,Avoid rebuilding entire tree,Minimal rebuild scope,Isolate changing widgets,setState on parent,Consumer only around changing widget,setState on root widget,High,
41,Performance,Use RepaintBoundary,Isolate repaints,RepaintBoundary for animations,Full screen repaints,RepaintBoundary(child: AnimatedWidget()),Animation without boundary,Medium,https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html
42,Performance,Profile with DevTools,Measure before optimizing,Flutter DevTools profiling,Guess at performance,DevTools performance tab,Optimize without measuring,Medium,https://docs.flutter.dev/tools/devtools
43,Accessibility,Use Semantics widget,Screen reader support,Semantics for accessibility,Missing accessibility info,Semantics(label: 'Submit button'),GestureDetector without semantics,High,https://api.flutter.dev/flutter/widgets/Semantics-class.html
44,Accessibility,Support large fonts,MediaQuery text scaling,MediaQuery.textScaleFactor,Fixed font sizes,style: Theme.of(context).textTheme,TextStyle(fontSize: 14),High,
45,Accessibility,Test with screen readers,TalkBack and VoiceOver,Test accessibility regularly,Skip accessibility testing,Regular TalkBack testing,No screen reader testing,High,
46,Testing,Use widget tests,Test widget behavior,WidgetTester for UI tests,Unit tests only,testWidgets('...' (tester) async {}),Only test() for UI,Medium,https://docs.flutter.dev/testing
47,Testing,Use integration tests,Full app testing,integration_test package,Manual testing only,IntegrationTestWidgetsFlutterBinding,Manual E2E testing,Medium,
48,Testing,Mock dependencies,Isolate tests,Mockito or mocktail,Real dependencies in tests,when(mock.method()).thenReturn(),Real API calls in tests,Medium,
49,Platform,Use Platform checks,Platform-specific code,Platform.isIOS Platform.isAndroid,Same code for all platforms,if (Platform.isIOS) {},Hardcoded iOS behavior,Medium,
50,Platform,Use kIsWeb for web,Web platform detection,kIsWeb for web checks,Platform for web,if (kIsWeb) {},Platform.isWeb (doesn't exist),Medium,
51,Packages,Use pub.dev packages,Community packages,Popular maintained packages,Custom implementations,cached_network_image,Custom image cache,Medium,https://pub.dev/
52,Packages,Check package quality,Quality before adding,Pub points and popularity,Any package without review,100+ pub points,Unmaintained packages,Medium,
.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Animation,Use Tailwind animate utilities,Built-in animations are optimized and respect reduced-motion,Use animate-pulse animate-spin animate-ping,Custom @keyframes for simple effects,animate-pulse,@keyframes pulse {...},Medium,https://tailwindcss.com/docs/animation
2,Animation,Limit bounce animations,Continuous bounce is distracting and causes motion sickness,Use animate-bounce sparingly on CTAs only,Multiple bounce animations on page,Single CTA with animate-bounce,5+ elements with animate-bounce,High,
3,Animation,Transition duration,Use appropriate transition speeds for UI feedback,duration-150 to duration-300 for UI,duration-1000 or longer for UI elements,transition-all duration-200,transition-all duration-1000,Medium,https://tailwindcss.com/docs/transition-duration
4,Animation,Hover transitions,Add smooth transitions on hover state changes,Add transition class with hover states,Instant hover changes without transition,hover:bg-gray-100 transition-colors,hover:bg-gray-100 (no transition),Low,
5,Z-Index,Use Tailwind z-* scale,Consistent stacking context with predefined scale,z-0 z-10 z-20 z-30 z-40 z-50,Arbitrary z-index values,z-50 for modals,z-[9999],Medium,https://tailwindcss.com/docs/z-index
6,Z-Index,Fixed elements z-index,Fixed navigation and modals need explicit z-index,z-50 for nav z-40 for dropdowns,Relying on DOM order for stacking,fixed top-0 z-50,fixed top-0 (no z-index),High,
7,Z-Index,Negative z-index for backgrounds,Use negative z-index for decorative backgrounds,z-[-1] for background elements,Positive z-index for backgrounds,-z-10 for decorative,z-10 for background,Low,
8,Layout,Container max-width,Limit content width for readability,max-w-7xl mx-auto for main content,Full-width content on large screens,max-w-7xl mx-auto px-4,w-full (no max-width),Medium,https://tailwindcss.com/docs/container
9,Layout,Responsive padding,Adjust padding for different screen sizes,px-4 md:px-6 lg:px-8,Same padding all sizes,px-4 sm:px-6 lg:px-8,px-8 (same all sizes),Medium,
10,Layout,Grid gaps,Use consistent gap utilities for spacing,gap-4 gap-6 gap-8,Margins on individual items,grid gap-6,grid with mb-4 on each item,Medium,https://tailwindcss.com/docs/gap
11,Layout,Flexbox alignment,Use flex utilities for alignment,items-center justify-between,Multiple nested wrappers,flex items-center justify-between,Nested divs for alignment,Low,
12,Images,Aspect ratio,Maintain consistent image aspect ratios,aspect-video aspect-square,No aspect ratio on containers,aspect-video rounded-lg,No aspect control,Medium,https://tailwindcss.com/docs/aspect-ratio
13,Images,Object fit,Control image scaling within containers,object-cover object-contain,Stretched distorted images,object-cover w-full h-full,No object-fit,Medium,https://tailwindcss.com/docs/object-fit
14,Images,Lazy loading,Defer loading of off-screen images,loading='lazy' on images,All images eager load,<img loading='lazy'>,<img> without lazy,High,
15,Images,Responsive images,Serve appropriate image sizes,srcset and sizes attributes,Same large image all devices,srcset with multiple sizes,4000px image everywhere,High,
16,Typography,Prose plugin,Use @tailwindcss/typography for rich text,prose prose-lg for article content,Custom styles for markdown,prose prose-lg max-w-none,Custom text styling,Medium,https://tailwindcss.com/docs/typography-plugin
17,Typography,Line height,Use appropriate line height for readability,leading-relaxed for body text,Default tight line height,leading-relaxed (1.625),leading-none or leading-tight,Medium,https://tailwindcss.com/docs/line-height
18,Typography,Font size scale,Use consistent text size scale,text-sm text-base text-lg text-xl,Arbitrary font sizes,text-lg,text-[17px],Low,https://tailwindcss.com/docs/font-size
19,Typography,Text truncation,Handle long text gracefully,truncate or line-clamp-*,Overflow breaking layout,line-clamp-2,No overflow handling,Medium,https://tailwindcss.com/docs/text-overflow
20,Colors,Opacity utilities,Use color opacity utilities,bg-black/50 text-white/80,Separate opacity class,bg-black/50,bg-black opacity-50,Low,https://tailwindcss.com/docs/background-color
21,Colors,Dark mode,Support dark mode with dark: prefix,dark:bg-gray-900 dark:text-white,No dark mode support,dark:bg-gray-900,Only light theme,Medium,https://tailwindcss.com/docs/dark-mode
22,Colors,Semantic colors,Use semantic color naming in config,primary secondary danger success,Generic color names in components,bg-primary,bg-blue-500 everywhere,Medium,
23,Spacing,Consistent spacing scale,Use Tailwind spacing scale consistently,p-4 m-6 gap-8,Arbitrary pixel values,p-4 (1rem),p-[15px],Low,https://tailwindcss.com/docs/customizing-spacing
24,Spacing,Negative margins,Use sparingly for overlapping effects,-mt-4 for overlapping elements,Negative margins for layout fixing,-mt-8 for card overlap,-m-2 to fix spacing issues,Medium,
25,Spacing,Space between,Use space-y-* for vertical lists,space-y-4 on flex/grid column,Margin on each child,space-y-4,Each child has mb-4,Low,https://tailwindcss.com/docs/space
26,Forms,Focus states,Always show focus indicators,focus:ring-2 focus:ring-blue-500,Remove focus outline,focus:ring-2 focus:ring-offset-2,focus:outline-none (no replacement),High,
27,Forms,Input sizing,Consistent input dimensions,h-10 px-3 for inputs,Inconsistent input heights,h-10 w-full px-3,Various heights per input,Medium,
28,Forms,Disabled states,Clear disabled styling,disabled:opacity-50 disabled:cursor-not-allowed,No disabled indication,disabled:opacity-50,Same style as enabled,Medium,
29,Forms,Placeholder styling,Style placeholder text appropriately,placeholder:text-gray-400,Dark placeholder text,placeholder:text-gray-400,Default dark placeholder,Low,
30,Responsive,Mobile-first approach,Start with mobile styles and add breakpoints,Default mobile + md: lg: xl:,Desktop-first approach,text-sm md:text-base,text-base max-md:text-sm,Medium,https://tailwindcss.com/docs/responsive-design
31,Responsive,Breakpoint testing,Test at standard breakpoints,320 375 768 1024 1280 1536,Only test on development device,Test all breakpoints,Single device testing,High,
32,Responsive,Hidden/shown utilities,Control visibility per breakpoint,hidden md:block,Different content per breakpoint,hidden md:flex,Separate mobile/desktop components,Low,https://tailwindcss.com/docs/display
33,Buttons,Button sizing,Consistent button dimensions,px-4 py-2 or px-6 py-3,Inconsistent button sizes,px-4 py-2 text-sm,Various padding per button,Medium,
34,Buttons,Touch targets,Minimum 44px touch target on mobile,min-h-[44px] on mobile,Small buttons on mobile,min-h-[44px] min-w-[44px],h-8 w-8 on mobile,High,
35,Buttons,Loading states,Show loading feedback,disabled + spinner icon,Clickable during loading,<Button disabled><Spinner/></Button>,Button without loading state,High,
36,Buttons,Icon buttons,Accessible icon-only buttons,aria-label on icon buttons,Icon button without label,<button aria-label='Close'><XIcon/></button>,<button><XIcon/></button>,High,
37,Cards,Card structure,Consistent card styling,rounded-lg shadow-md p-6,Inconsistent card styles,rounded-2xl shadow-lg p-6,Mixed card styling,Low,
38,Cards,Card hover states,Interactive cards should have hover feedback,hover:shadow-lg transition-shadow,No hover on clickable cards,hover:shadow-xl transition-shadow,Static cards that are clickable,Medium,
39,Cards,Card spacing,Consistent internal card spacing,space-y-4 for card content,Inconsistent internal spacing,space-y-4 or p-6,Mixed mb-2 mb-4 mb-6,Low,
40,Accessibility,Screen reader text,Provide context for screen readers,sr-only for hidden labels,Missing context for icons,<span class='sr-only'>Close menu</span>,No label for icon button,High,https://tailwindcss.com/docs/screen-readers
41,Accessibility,Focus visible,Show focus only for keyboard users,focus-visible:ring-2,Focus on all interactions,focus-visible:ring-2,focus:ring-2 (shows on click too),Medium,
42,Accessibility,Reduced motion,Respect user motion preferences,motion-reduce:animate-none,Ignore motion preferences,motion-reduce:transition-none,No reduced motion support,High,https://tailwindcss.com/docs/hover-focus-and-other-states#prefers-reduced-motion
43,Performance,Configure content paths,Tailwind needs to know where classes are used,Use 'content' array in config,Use deprecated 'purge' option (v2),"content: ['./src/**/*.{js,ts,jsx,tsx}']",purge: [...],High,https://tailwindcss.com/docs/content-configuration
44,Performance,JIT mode,Use JIT for faster builds and smaller bundles,JIT enabled (default in v3),Full CSS in development,Tailwind v3 defaults,Tailwind v2 without JIT,Medium,
45,Performance,Avoid @apply bloat,Use @apply sparingly,Direct utilities in HTML,Heavy @apply usage,class='px-4 py-2 rounded',@apply px-4 py-2 rounded;,Low,https://tailwindcss.com/docs/reusing-styles
46,Plugins,Official plugins,Use official Tailwind plugins,@tailwindcss/forms typography aspect-ratio,Custom implementations,@tailwindcss/forms,Custom form reset CSS,Medium,https://tailwindcss.com/docs/plugins
47,Plugins,Custom utilities,Create utilities for repeated patterns,Custom utility in config,Repeated arbitrary values,Custom shadow utility,"shadow-[0_4px_20px_rgba(0,0,0,0.1)] everywhere",Medium,
48,Layout,Container Queries,Use @container for component-based responsiveness,Use @container and @lg: etc.,Media queries for component internals,@container @lg:grid-cols-2,@media (min-width: ...) inside component,Medium,https://github.com/tailwindlabs/tailwindcss-container-queries
49,Interactivity,Group and Peer,Style based on parent/sibling state,group-hover peer-checked,JS for simple state interactions,group-hover:text-blue-500,onMouseEnter={() => setHover(true)},Low,https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
50,Customization,Arbitrary Values,Use [] for one-off values,w-[350px] for specific needs,Creating config for single use,top-[117px] (if strictly needed),style={{ top: '117px' }},Low,https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values
51,Colors,Theme color variables,Define colors in Tailwind theme and use directly,bg-primary text-success border-cta,bg-[var(--color-primary)] text-[var(--color-success)],bg-primary,bg-[var(--color-primary)],Medium,https://tailwindcss.com/docs/customizing-colors
52,Colors,Use bg-linear-to-* for gradients,Tailwind v4 uses bg-linear-to-* syntax for gradients,bg-linear-to-r bg-linear-to-b,bg-gradient-to-* (deprecated in v4),bg-linear-to-r from-blue-500 to-purple-500,bg-gradient-to-r from-blue-500 to-purple-500,Medium,https://tailwindcss.com/docs/background-image
53,Layout,Use shrink-0 shorthand,Shorter class name for flex-shrink-0,shrink-0 shrink,flex-shrink-0 flex-shrink,shrink-0,flex-shrink-0,Low,https://tailwindcss.com/docs/flex-shrink
54,Layout,Use size-* for square dimensions,Single utility for equal width and height,size-4 size-8 size-12,Separate h-* w-* for squares,size-6,h-6 w-6,Low,https://tailwindcss.com/docs/size
55,Images,SVG explicit dimensions,Add width/height attributes to SVGs to prevent layout shift before CSS loads,<svg class='size-6' width='24' height='24'>,SVG without explicit dimensions,<svg class='size-6' width='24' height='24'>,<svg class='size-6'>,High,
.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Composable,Pure UI composables,Composable functions should only render UI,Accept state and callbacks,Calling usecase/repo,Pure UI composable,Business logic in UI,High,https://developer.android.com/jetpack/compose/mental-model
2,Composable,Small composables,Each composable has single responsibility,Split into components,Huge composable,Reusable UI,Monolithic UI,Medium,
3,Composable,Stateless by default,Prefer stateless composables,Hoist state,Local mutable state,Stateless UI,Hidden state,High,https://developer.android.com/jetpack/compose/state#state-hoisting
4,State,Single source of truth,UI state comes from one source,StateFlow from VM,Multiple states,Unified UiState,Scattered state,High,https://developer.android.com/topic/architecture/ui-layer
5,State,Model UI State,Use sealed interface/data class,UiState.Loading,Boolean flags,Explicit state,Flag hell,High,
6,State,remember only UI state,remember for UI-only state,"Scroll, animation",Business state,Correct remember,Misuse remember,High,https://developer.android.com/jetpack/compose/state
7,State,rememberSaveable,Persist state across config,rememberSaveable,remember,State survives,State lost,High,https://developer.android.com/jetpack/compose/state#restore-ui-state
8,State,derivedStateOf,Optimize recomposition,derivedStateOf,Recompute always,Optimized,Jank,Medium,https://developer.android.com/jetpack/compose/performance
9,SideEffect,LaunchedEffect keys,Use correct keys,LaunchedEffect(id),LaunchedEffect(Unit),Scoped effect,Infinite loop,High,https://developer.android.com/jetpack/compose/side-effects
10,SideEffect,rememberUpdatedState,Avoid stale lambdas,rememberUpdatedState,Capture directly,Safe callback,Stale state,Medium,https://developer.android.com/jetpack/compose/side-effects
11,SideEffect,DisposableEffect,Clean up resources,onDispose,No cleanup,No leak,Memory leak,High,
12,Architecture,Unidirectional data flow,UI â†’ VM â†’ State,onEvent,Two-way binding,Predictable flow,Hard debug,High,https://developer.android.com/topic/architecture
13,Architecture,No business logic in UI,Logic belongs to VM,Collect state,Call repo,Clean UI,Fat UI,High,
14,Architecture,Expose immutable state,Expose StateFlow,asStateFlow,Mutable exposed,Safe API,State mutation,High,
15,Lifecycle,Lifecycle-aware collect,Use collectAsStateWithLifecycle,Lifecycle aware,collectAsState,No leak,Leak,High,https://developer.android.com/jetpack/compose/lifecycle
16,Navigation,Event-based navigation,VM emits navigation event,"VM: Channel + receiveAsFlow(), V: Collect with Dispatchers.Main.immediate",Nav in UI,Decoupled nav,Using State / SharedFlow for navigation -> event is replayed and navigation fires again (StateFlow),High,https://developer.android.com/jetpack/compose/navigation
17,Navigation,Typed routes,Use sealed routes,sealed class Route,String routes,Type-safe,Runtime crash,Medium,
18,Performance,Stable parameters,Prefer immutable/stable params,@Immutable,Mutable params,Stable recomposition,Extra recomposition,High,https://developer.android.com/jetpack/compose/performance
19,Performance,Use key in Lazy,Provide stable keys,key=id,No key,Stable list,Item jump,High,
20,Performance,Avoid heavy work,No heavy computation in UI,Precompute in VM,Compute in UI,Smooth UI,Jank,High,
21,Performance,Remember expensive objects,remember heavy objects,remember,Recreate each recomposition,Efficient,Wasteful,Medium,
22,Theming,Design system,Centralized theme,Material3 tokens,Hardcoded values,Consistent UI,Inconsistent,High,https://developer.android.com/jetpack/compose/themes
23,Theming,Dark mode support,Theme-based colors,colorScheme,Fixed color,Adaptive UI,Broken dark,Medium,
24,Layout,Prefer Modifier over extra layouts,Use Modifier to adjust layout instead of adding wrapper composables,Use Modifier.padding(),Wrap content with extra Box,Padding via modifier,Box just for padding,High,https://developer.android.com/jetpack/compose/modifiers
25,Layout,Avoid deep layout nesting,Deep layout trees increase measure & layout cost,Keep layout flat,Box ? Column ? Box ? Row,Flat hierarchy,Deep nested tree,High,
26,Layout,Use Row/Column for linear layout,Linear layouts are simpler and more performant,Use Row / Column,Custom layout for simple cases,Row/Column usage,Over-engineered layout,High,
27,Layout,Use Box only for overlapping content,Box should be used only when children overlap,Stack elements,Use Box as Column,Proper overlay,Misused Box,Medium,
28,Layout,Prefer LazyColumn over Column scroll,Lazy layouts are virtualized and efficient,LazyColumn,Column.verticalScroll(),Lazy list,Scrollable Column,High,https://developer.android.com/jetpack/compose/lists
29,Layout,Avoid nested scroll containers,Nested scrolling causes UX & performance issues,Single scroll container,Scroll inside scroll,One scroll per screen,Nested scroll,High,
30,Layout,Avoid fillMaxSize by default,fillMaxSize may break parent constraints,Use exact size,Fill max everywhere,Constraint-aware size,Overfilled layout,Medium,
31,Layout,Avoid intrinsic size unless necessary,Intrinsic measurement is expensive,Explicit sizing,IntrinsicSize.Min,Predictable layout,Expensive measure,High,https://developer.android.com/jetpack/compose/layout/intrinsics
32,Layout,Use Arrangement and Alignment APIs,Declare layout intent explicitly,Use Arrangement / Alignment,Manual spacing hacks,Declarative spacing,Magic spacing,High,
33,Layout,Extract reusable layout patterns,Repeated layouts should be shared,Create layout composable,Copy-paste layouts,Reusable scaffold,Duplicated layout,High,
34,Theming,No hardcoded text style,Use typography,MaterialTheme.typography,Hardcode sp,Scalable,Inconsistent,Medium,
35,Testing,Stateless UI testing,Composable easy to test,Pass state,Hidden state,Testable,Hard test,High,https://developer.android.com/jetpack/compose/testing
36,Testing,Use testTag,Stable UI selectors,Modifier.testTag,Find by text,Stable tests,Flaky tests,Medium,
37,Preview,Multiple previews,Preview multiple states,@Preview,Single preview,Better dev UX,Misleading,Low,https://developer.android.com/jetpack/compose/tooling/preview
38,DI,Inject VM via Hilt,Use hiltViewModel,@HiltViewModel,Manual VM,Clean DI,Coupling,High,https://developer.android.com/training/dependency-injection/hilt-jetpack
39,DI,No DI in UI,Inject in VM,Constructor inject,Inject composable,Proper scope,Wrong scope,High,
40,Accessibility,Content description,Accessible UI,contentDescription,Ignore a11y,Inclusive,A11y fail,Medium,https://developer.android.com/jetpack/compose/accessibility
41,Accessibility,Semantics,Use semantics API,Modifier.semantics,None,Testable a11y,Invisible,Medium,
42,Animation,Compose animation APIs,Use animate*AsState,AnimatedVisibility,Manual anim,Smooth,Jank,Medium,https://developer.android.com/jetpack/compose/animation
43,Animation,Avoid animation logic in VM,Animation is UI concern,Animate in UI,Animate in VM,Correct layering,Mixed concern,Low,
44,Modularization,Feature-based UI modules,UI per feature,:feature:ui,God module,Scalable,Tight coupling,High,https://developer.android.com/topic/modularization
45,Modularization,Public UI contracts,Expose minimal UI API,Interface/Route,Expose impl,Encapsulated,Leaky module,Medium,
46,State,Snapshot state only,Use Compose state,mutableStateOf,Custom observable,Compose aware,Buggy UI,Medium,
47,State,Avoid mutable collections,Immutable list/map,PersistentList,MutableList,Stable UI,Silent bug,High,
48,Lifecycle,RememberCoroutineScope usage,Only for UI jobs,UI coroutine,Long jobs,Scoped job,Leak,Medium,https://developer.android.com/jetpack/compose/side-effects#remembercoroutinescope
49,Interop,Interop View carefully,Use AndroidView,Isolated usage,Mix everywhere,Safe interop,Messy UI,Low,https://developer.android.com/jetpack/compose/interop
50,Interop,Avoid legacy patterns,No LiveData in UI,StateFlow,LiveData,Modern stack,Legacy debt,Medium,
51,Debug,Use layout inspector,Inspect recomposition,Tools,Blind debug,Fast debug,Guessing,Low,https://developer.android.com/studio/debug/layout-inspector
52,Debug,Enable recomposition counts,Track recomposition,Debug flags,Ignore,Performance aware,Hidden jank,Low,
.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Routing,Use App Router for new projects,App Router is the recommended approach in Next.js 14+,app/ directory with page.tsx,pages/ for new projects,app/dashboard/page.tsx,pages/dashboard.tsx,Medium,https://nextjs.org/docs/app
2,Routing,Use file-based routing,Create routes by adding files in app directory,page.tsx for routes layout.tsx for layouts,Manual route configuration,app/blog/[slug]/page.tsx,Custom router setup,Medium,https://nextjs.org/docs/app/building-your-application/routing
3,Routing,Colocate related files,Keep components styles tests with their routes,Component files alongside page.tsx,Separate components folder,app/dashboard/_components/,components/dashboard/,Low,
4,Routing,Use route groups for organization,Group routes without affecting URL,Parentheses for route groups,Nested folders affecting URL,(marketing)/about/page.tsx,marketing/about/page.tsx,Low,https://nextjs.org/docs/app/building-your-application/routing/route-groups
5,Routing,Handle loading states,Use loading.tsx for route loading UI,loading.tsx alongside page.tsx,Manual loading state management,app/dashboard/loading.tsx,useState for loading in page,Medium,https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming
6,Routing,Handle errors with error.tsx,Catch errors at route level,error.tsx with reset function,try/catch in every component,app/dashboard/error.tsx,try/catch in page component,High,https://nextjs.org/docs/app/building-your-application/routing/error-handling
7,Rendering,Use Server Components by default,Server Components reduce client JS bundle,Keep components server by default,Add 'use client' unnecessarily,export default function Page(),('use client') for static content,High,https://nextjs.org/docs/app/building-your-application/rendering/server-components
8,Rendering,Mark Client Components explicitly,'use client' for interactive components,Add 'use client' only when needed,Server Component with hooks/events,('use client') for onClick useState,No directive with useState,High,https://nextjs.org/docs/app/building-your-application/rendering/client-components
9,Rendering,Push Client Components down,Keep Client Components as leaf nodes,Client wrapper for interactive parts only,Mark page as Client Component,<InteractiveButton/> in Server Page,('use client') on page.tsx,High,
10,Rendering,Use streaming for better UX,Stream content with Suspense boundaries,Suspense for slow data fetches,Wait for all data before render,<Suspense><SlowComponent/></Suspense>,await allData then render,Medium,https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming
11,Rendering,Choose correct rendering strategy,SSG for static SSR for dynamic ISR for semi-static,generateStaticParams for known paths,SSR for static content,export const revalidate = 3600,fetch without cache config,Medium,
12,DataFetching,Fetch data in Server Components,Fetch directly in async Server Components,async function Page() { const data = await fetch() },useEffect for initial data,const data = await fetch(url),useEffect(() => fetch(url)),High,https://nextjs.org/docs/app/building-your-application/data-fetching
13,DataFetching,Configure caching explicitly (Next.js 15+),Next.js 15 changed defaults to uncached for fetch,Explicitly set cache: 'force-cache' for static data,Assume default is cached (it's not in Next.js 15),fetch(url { cache: 'force-cache' }),fetch(url) // Uncached in v15,High,https://nextjs.org/docs/app/building-your-application/upgrading/version-15
14,DataFetching,Deduplicate fetch requests,React and Next.js dedupe same requests,Same fetch call in multiple components,Manual request deduplication,Multiple components fetch same URL,Custom cache layer,Low,
15,DataFetching,Use Server Actions for mutations,Server Actions for form submissions,action={serverAction} in forms,API route for every mutation,<form action={createPost}>,<form onSubmit={callApiRoute}>,Medium,https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations
16,DataFetching,Revalidate data appropriately,Use revalidatePath/revalidateTag after mutations,Revalidate after Server Action,'use client' with manual refetch,revalidatePath('/posts'),router.refresh() everywhere,Medium,https://nextjs.org/docs/app/building-your-application/caching#revalidating
17,Images,Use next/image for optimization,Automatic image optimization and lazy loading,<Image> component for all images,<img> tags directly,<Image src={} alt={} width={} height={}>,<img src={}/>,High,https://nextjs.org/docs/app/building-your-application/optimizing/images
18,Images,Provide width and height,Prevent layout shift with dimensions,width and height props or fill,Missing dimensions,<Image width={400} height={300}/>,<Image src={url}/>,High,
19,Images,Use fill for responsive images,Fill container with object-fit,fill prop with relative parent,Fixed dimensions for responsive,"<Image fill className=""object-cover""/>",<Image width={window.width}/>,Medium,
20,Images,Configure remote image domains,Whitelist external image sources,remotePatterns in next.config.js,Allow all domains,remotePatterns: [{ hostname: 'cdn.example.com' }],domains: ['*'],High,https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
21,Images,Use priority for LCP images,Mark above-fold images as priority,priority prop on hero images,All images with priority,<Image priority src={hero}/>,<Image priority/> on every image,Medium,
22,Fonts,Use next/font for fonts,Self-hosted fonts with zero layout shift,next/font/google or next/font/local,External font links,import { Inter } from 'next/font/google',"<link href=""fonts.googleapis.com""/>",Medium,https://nextjs.org/docs/app/building-your-application/optimizing/fonts
23,Fonts,Apply font to layout,Set font in root layout for consistency,className on body in layout.tsx,Font in individual pages,<body className={inter.className}>,Each page imports font,Low,
24,Fonts,Use variable fonts,Variable fonts reduce bundle size,Single variable font file,Multiple font weights as files,Inter({ subsets: ['latin'] }),Inter_400 Inter_500 Inter_700,Low,
25,Metadata,Use generateMetadata for dynamic,Generate metadata based on params,export async function generateMetadata(),Hardcoded metadata everywhere,generateMetadata({ params }),export const metadata = {},Medium,https://nextjs.org/docs/app/building-your-application/optimizing/metadata
26,Metadata,Include OpenGraph images,Add OG images for social sharing,opengraph-image.tsx or og property,Missing social preview images,opengraph: { images: ['/og.png'] },No OG configuration,Medium,
27,Metadata,Use metadata API,Export metadata object for static metadata,export const metadata = {},Manual head tags,export const metadata = { title: 'Page' },<head><title>Page</title></head>,Medium,
28,API,Use Route Handlers for APIs,app/api routes for API endpoints,app/api/users/route.ts,pages/api for new projects,export async function GET(request),export default function handler,Medium,https://nextjs.org/docs/app/building-your-application/routing/route-handlers
29,API,Return proper Response objects,Use NextResponse for API responses,NextResponse.json() for JSON,Plain objects or res.json(),return NextResponse.json({ data }),return { data },Medium,
30,API,Handle HTTP methods explicitly,Export named functions for methods,Export GET POST PUT DELETE,Single handler for all methods,export async function POST(),switch(req.method),Low,
31,API,Validate request body,Validate input before processing,Zod or similar for validation,Trust client input,const body = schema.parse(await req.json()),const body = await req.json(),High,
32,Middleware,Use middleware for auth,Protect routes with middleware.ts,middleware.ts at root,Auth check in every page,export function middleware(request),if (!session) redirect in page,Medium,https://nextjs.org/docs/app/building-your-application/routing/middleware
33,Middleware,Match specific paths,Configure middleware matcher,config.matcher for specific routes,Run middleware on all routes,matcher: ['/dashboard/:path*'],No matcher config,Medium,
34,Middleware,Keep middleware edge-compatible,Middleware runs on Edge runtime,Edge-compatible code only,Node.js APIs in middleware,Edge-compatible auth check,fs.readFile in middleware,High,
35,Environment,Use NEXT_PUBLIC prefix,Client-accessible env vars need prefix,NEXT_PUBLIC_ for client vars,Server vars exposed to client,NEXT_PUBLIC_API_URL,API_SECRET in client code,High,https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
36,Environment,Validate env vars,Check required env vars exist,Validate on startup,Undefined env at runtime,if (!process.env.DATABASE_URL) throw,process.env.DATABASE_URL (might be undefined),High,
37,Environment,Use .env.local for secrets,Local env file for development secrets,.env.local gitignored,Secrets in .env committed,.env.local with secrets,.env with DATABASE_PASSWORD,High,
38,Performance,Analyze bundle size,Use @next/bundle-analyzer,Bundle analyzer in dev,Ship large bundles blindly,ANALYZE=true npm run build,No bundle analysis,Medium,https://nextjs.org/docs/app/building-your-application/optimizing/bundle-analyzer
39,Performance,Use dynamic imports,Code split with next/dynamic,dynamic() for heavy components,Import everything statically,const Chart = dynamic(() => import('./Chart')),import Chart from './Chart',Medium,https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading
40,Performance,Avoid layout shifts,Reserve space for dynamic content,Skeleton loaders aspect ratios,Content popping in,"<Skeleton className=""h-48""/>",No placeholder for async content,High,
41,Performance,Use Partial Prerendering,Combine static and dynamic in one route,Static shell with Suspense holes,Full dynamic or static pages,Static header + dynamic content,Entire page SSR,Low,https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering
42,Link,Use next/link for navigation,Client-side navigation with prefetching,"<Link href=""""> for internal links",<a> for internal navigation,"<Link href=""/about"">About</Link>","<a href=""/about"">About</a>",High,https://nextjs.org/docs/app/api-reference/components/link
43,Link,Prefetch strategically,Control prefetching behavior,prefetch={false} for low-priority,Prefetch all links,<Link prefetch={false}>,Default prefetch on every link,Low,
44,Link,Use scroll option appropriately,Control scroll behavior on navigation,scroll={false} for tabs pagination,Always scroll to top,<Link scroll={false}>,Manual scroll management,Low,
45,Config,Use next.config.js correctly,Configure Next.js behavior,Proper config options,Deprecated or wrong options,images: { remotePatterns: [] },images: { domains: [] },Medium,https://nextjs.org/docs/app/api-reference/next-config-js
46,Config,Enable strict mode,Catch potential issues early,reactStrictMode: true,Strict mode disabled,reactStrictMode: true,reactStrictMode: false,Medium,
47,Config,Configure redirects and rewrites,Use config for URL management,redirects() rewrites() in config,Manual redirect handling,redirects: async () => [...],res.redirect in pages,Medium,https://nextjs.org/docs/app/api-reference/next-config-js/redirects
48,Deployment,Use Vercel for easiest deploy,Vercel optimized for Next.js,Deploy to Vercel,Self-host without knowledge,vercel deploy,Complex Docker setup for simple app,Low,https://nextjs.org/docs/app/building-your-application/deploying
49,Deployment,Configure output for self-hosting,Set output option for deployment target,output: 'standalone' for Docker,Default output for containers,output: 'standalone',No output config for Docker,Medium,https://nextjs.org/docs/app/building-your-application/deploying#self-hosting
50,Security,Sanitize user input,Never trust user input,Escape sanitize validate all input,Direct interpolation of user data,DOMPurify.sanitize(userInput),dangerouslySetInnerHTML={{ __html: userInput }},High,
51,Security,Use CSP headers,Content Security Policy for XSS protection,Configure CSP in next.config.js,No security headers,headers() with CSP,No CSP configuration,High,https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy
52,Security,Validate Server Action input,Server Actions are public endpoints,Validate and authorize in Server Action,Trust Server Action input,Auth check + validation in action,Direct database call without check,High,
.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Installation,Add Nuxt UI module,Install and configure Nuxt UI in your Nuxt project,pnpm add @nuxt/ui and add to modules,Manual component imports,"modules: ['@nuxt/ui']","import { UButton } from '@nuxt/ui'",High,https://ui.nuxt.com/docs/getting-started/installation/nuxt
2,Installation,Import Tailwind and Nuxt UI CSS,Required CSS imports in main.css file,@import tailwindcss and @import @nuxt/ui,Skip CSS imports,"@import ""tailwindcss""; @import ""@nuxt/ui"";",No CSS imports,High,https://ui.nuxt.com/docs/getting-started/installation/nuxt
3,Installation,Wrap app with UApp component,UApp provides global configs for Toast Tooltip and overlays,<UApp> wrapper in app.vue,Skip UApp wrapper,<UApp><NuxtPage/></UApp>,<NuxtPage/> without wrapper,High,https://ui.nuxt.com/docs/components/app
4,Components,Use U prefix for components,All Nuxt UI components use U prefix by default,UButton UInput UModal,Button Input Modal,<UButton>Click</UButton>,<Button>Click</Button>,Medium,https://ui.nuxt.com/docs/getting-started/installation/nuxt
5,Components,Use semantic color props,Use semantic colors like primary secondary error,color="primary" color="error",Hardcoded colors,"<UButton color=""primary"">","<UButton class=""bg-green-500"">",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system
6,Components,Use variant prop for styling,Nuxt UI provides solid outline soft subtle ghost link variants,variant="soft" variant="outline",Custom button classes,"<UButton variant=""soft"">","<UButton class=""border bg-transparent"">",Medium,https://ui.nuxt.com/docs/components/button
7,Components,Use size prop consistently,Components support xs sm md lg xl sizes,size="sm" size="lg",Arbitrary sizing classes,"<UButton size=""lg"">","<UButton class=""text-xl px-6"">",Low,https://ui.nuxt.com/docs/components/button
8,Icons,Use icon prop with Iconify format,Nuxt UI supports Iconify icons via icon prop,icon="lucide:home" icon="heroicons:user",i-lucide-home format,"<UButton icon=""lucide:home"">","<UButton icon=""i-lucide-home"">",Medium,https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt
9,Icons,Use leadingIcon and trailingIcon,Position icons with dedicated props for clarity,leadingIcon="lucide:plus" trailingIcon="lucide:arrow-right",Manual icon positioning,"<UButton leadingIcon=""lucide:plus"">","<UButton><Icon name=""lucide:plus""/>Add</UButton>",Low,https://ui.nuxt.com/docs/components/button
10,Theming,Configure colors in app.config.ts,Runtime color configuration without restart,ui.colors.primary in app.config.ts,Hardcoded colors in components,"defineAppConfig({ ui: { colors: { primary: 'blue' } } })","<UButton class=""bg-blue-500"">",High,https://ui.nuxt.com/docs/getting-started/theme/design-system
11,Theming,Use @theme directive for custom colors,Define design tokens in CSS with Tailwind @theme,@theme { --color-brand-500: #xxx },Inline color definitions,@theme { --color-brand-500: #ef4444; },:style="{ color: '#ef4444' }",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system
12,Theming,Extend semantic colors in nuxt.config,Register new colors like tertiary in theme.colors,theme.colors array in ui config,Use undefined colors,"ui: { theme: { colors: ['primary', 'tertiary'] } }","<UButton color=""tertiary""> without config",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system
13,Forms,Use UForm with schema validation,UForm supports Zod Yup Joi Valibot schemas,:schema prop with validation schema,Manual form validation,"<UForm :schema=""schema"" :state=""state"">",Manual @blur validation,High,https://ui.nuxt.com/docs/components/form
14,Forms,Use UFormField for field wrapper,Provides label error message and validation display,UFormField with name prop,Manual error handling,"<UFormField name=""email"" label=""Email"">",<div><label>Email</label><UInput/><span>error</span></div>,Medium,https://ui.nuxt.com/docs/components/form-field
15,Forms,Handle form submit with @submit,UForm emits submit event with validated data,@submit handler on UForm,@click on submit button,"<UForm @submit=""onSubmit"">","<UButton @click=""onSubmit"">",Medium,https://ui.nuxt.com/docs/components/form
16,Forms,Use validateOn prop for validation timing,Control when validation triggers (blur change input),validateOn="['blur']" for performance,Always validate on input,"<UForm :validateOn=""['blur', 'change']"">","<UForm> (validates on every keystroke)",Low,https://ui.nuxt.com/docs/components/form
17,Overlays,Use v-model:open for overlay control,Modal Slideover Drawer use v-model:open,v-model:open for controlled state,Manual show/hide logic,"<UModal v-model:open=""isOpen"">",<UModal v-if="isOpen">,Medium,https://ui.nuxt.com/docs/components/modal
18,Overlays,Use useOverlay composable for programmatic overlays,Open overlays programmatically without template refs,useOverlay().open(MyModal),Template ref and manual control,"const overlay = useOverlay(); overlay.open(MyModal, { props })","const modal = ref(); modal.value.open()",Medium,https://ui.nuxt.com/docs/components/modal
19,Overlays,Use title and description props,Built-in header support for overlays,title="Confirm" description="Are you sure?",Manual header content,"<UModal title=""Confirm"" description=""Are you sure?"">","<UModal><template #header><h2>Confirm</h2></template>",Low,https://ui.nuxt.com/docs/components/modal
20,Dashboard,Use UDashboardSidebar for navigation,Provides collapsible resizable sidebar with mobile support,UDashboardSidebar with header default footer slots,Custom sidebar implementation,<UDashboardSidebar><template #header>...</template></UDashboardSidebar>,<aside class="w-64 border-r">,Medium,https://ui.nuxt.com/docs/components/dashboard-sidebar
21,Dashboard,Use UDashboardGroup for layout,Wraps dashboard components with sidebar state management,UDashboardGroup > UDashboardSidebar + UDashboardPanel,Manual layout flex containers,<UDashboardGroup><UDashboardSidebar/><UDashboardPanel/></UDashboardGroup>,"<div class=""flex""><aside/><main/></div>",Medium,https://ui.nuxt.com/docs/components/dashboard-group
22,Dashboard,Use UDashboardNavbar for top navigation,Responsive navbar with mobile menu support,UDashboardNavbar in dashboard layout,Custom navbar implementation,<UDashboardNavbar :links="navLinks"/>,<nav class="border-b">,Low,https://ui.nuxt.com/docs/components/dashboard-navbar
23,Tables,Use UTable with data and columns props,Powered by TanStack Table with built-in features,:data and :columns props,Manual table markup,"<UTable :data=""users"" :columns=""columns""/>","<table><tr v-for=""user in users"">",High,https://ui.nuxt.com/docs/components/table
24,Tables,Define columns with accessorKey,Column definitions use accessorKey for data binding,accessorKey: 'email' in column def,String column names only,"{ accessorKey: 'email', header: 'Email' }","['name', 'email']",Medium,https://ui.nuxt.com/docs/components/table
25,Tables,Use cell slot for custom rendering,Customize cell content with scoped slots,#cell-columnName slot,Override entire table,<template #cell-status="{ row }">,Manual column render function,Medium,https://ui.nuxt.com/docs/components/table
26,Tables,Enable sorting with sortable column option,Add sortable: true to column definition,sortable: true in column,Manual sort implementation,"{ accessorKey: 'name', sortable: true }",@click="sortBy('name')",Low,https://ui.nuxt.com/docs/components/table
27,Navigation,Use UNavigationMenu for nav links,Horizontal or vertical navigation with dropdown support,UNavigationMenu with items array,Manual nav with v-for,"<UNavigationMenu :items=""navItems""/>","<nav><a v-for=""item in items"">",Medium,https://ui.nuxt.com/docs/components/navigation-menu
28,Navigation,Use UBreadcrumb for page hierarchy,Automatic breadcrumb with NuxtLink support,:items array with label and to,Manual breadcrumb links,"<UBreadcrumb :items=""breadcrumbs""/>","<nav><span v-for=""crumb in crumbs"">",Low,https://ui.nuxt.com/docs/components/breadcrumb
29,Navigation,Use UTabs for tabbed content,Tab navigation with content panels,UTabs with items containing slot content,Manual tab state,"<UTabs :items=""tabs""/>","<div><button @click=""tab=1"">",Medium,https://ui.nuxt.com/docs/components/tabs
30,Feedback,Use useToast for notifications,Composable for toast notifications,useToast().add({ title description }),Alert components for toasts,"const toast = useToast(); toast.add({ title: 'Saved' })",<UAlert v-if="showSuccess">,High,https://ui.nuxt.com/docs/components/toast
31,Feedback,Use UAlert for inline messages,Static alert messages with icon and actions,UAlert with title description color,Toast for static messages,"<UAlert title=""Warning"" color=""warning""/>",useToast for inline alerts,Medium,https://ui.nuxt.com/docs/components/alert
32,Feedback,Use USkeleton for loading states,Placeholder content during data loading,USkeleton with appropriate size,Spinner for content loading,<USkeleton class="h-4 w-32"/>,<UIcon name="lucide:loader" class="animate-spin"/>,Low,https://ui.nuxt.com/docs/components/skeleton
33,Color Mode,Use UColorModeButton for theme toggle,Built-in light/dark mode toggle button,UColorModeButton component,Manual color mode logic,<UColorModeButton/>,"<button @click=""toggleColorMode"">",Low,https://ui.nuxt.com/docs/components/color-mode-button
34,Color Mode,Use UColorModeSelect for theme picker,Dropdown to select system light or dark mode,UColorModeSelect component,Custom select for theme,<UColorModeSelect/>,"<USelect v-model=""colorMode"" :items=""modes""/>",Low,https://ui.nuxt.com/docs/components/color-mode-select
35,Customization,Use ui prop for component styling,Override component styles via ui prop,ui prop with slot class overrides,Global CSS overrides,"<UButton :ui=""{ base: 'rounded-full' }""/>",<UButton class="!rounded-full"/>,Medium,https://ui.nuxt.com/docs/getting-started/theme/components
36,Customization,Configure default variants in nuxt.config,Set default color and size for all components,theme.defaultVariants in ui config,Repeat props on every component,"ui: { theme: { defaultVariants: { color: 'neutral' } } }","<UButton color=""neutral""> everywhere",Medium,https://ui.nuxt.com/docs/getting-started/installation/nuxt
37,Customization,Use app.config.ts for theme overrides,Runtime theme customization,defineAppConfig with ui key,nuxt.config for runtime values,"defineAppConfig({ ui: { button: { defaultVariants: { size: 'sm' } } } })","nuxt.config ui.button.size: 'sm'",Medium,https://ui.nuxt.com/docs/getting-started/theme/components
38,Performance,Enable component detection,Tree-shake unused component CSS,experimental.componentDetection: true,Include all component CSS,"ui: { experimental: { componentDetection: true } }","ui: {} (includes all CSS)",Low,https://ui.nuxt.com/docs/getting-started/installation/nuxt
39,Performance,Use UTable virtualize for large data,Enable virtualization for 1000+ rows,:virtualize prop on UTable,Render all rows,"<UTable :data=""largeData"" virtualize/>","<UTable :data=""largeData""/>",Medium,https://ui.nuxt.com/docs/components/table
40,Accessibility,Use semantic component props,Components have built-in ARIA support,Use title description label props,Skip accessibility props,"<UModal title=""Settings"">","<UModal><h2>Settings</h2>",Medium,https://ui.nuxt.com/docs/components/modal
41,Accessibility,Use UFormField for form accessibility,Automatic label-input association,UFormField wraps inputs,Manual id and for attributes,"<UFormField label=""Email""><UInput/></UFormField>","<label for=""email"">Email</label><UInput id=""email""/>",High,https://ui.nuxt.com/docs/components/form-field
42,Content,Use UContentToc for table of contents,Automatic TOC with active heading highlight,UContentToc with :links,Manual TOC implementation,"<UContentToc :links=""toc""/>","<nav><a v-for=""heading in headings"">",Low,https://ui.nuxt.com/docs/components/content-toc
43,Content,Use UContentSearch for docs search,Command palette for documentation search,UContentSearch with Nuxt Content,Custom search implementation,<UContentSearch/>,<UCommandPalette :groups="searchResults"/>,Low,https://ui.nuxt.com/docs/components/content-search
44,AI/Chat,Use UChatMessages for chat UI,Designed for Vercel AI SDK integration,UChatMessages with messages array,Custom chat message list,"<UChatMessages :messages=""messages""/>","<div v-for=""msg in messages"">",Medium,https://ui.nuxt.com/docs/components/chat-messages
45,AI/Chat,Use UChatPrompt for input,Enhanced textarea for AI prompts,UChatPrompt with v-model,Basic textarea,<UChatPrompt v-model="prompt"/>,<UTextarea v-model="prompt"/>,Medium,https://ui.nuxt.com/docs/components/chat-prompt
46,Editor,Use UEditor for rich text,TipTap-based editor with toolbar support,UEditor with v-model:content,Custom TipTap setup,"<UEditor v-model:content=""content""/>",Manual TipTap initialization,Medium,https://ui.nuxt.com/docs/components/editor
47,Links,Use to prop for navigation,UButton and ULink support NuxtLink to prop,to="/dashboard" for internal links,href for internal navigation,"<UButton to=""/dashboard"">","<UButton href=""/dashboard"">",Medium,https://ui.nuxt.com/docs/components/button
48,Links,Use external prop for outside links,Explicitly mark external links,target="_blank" with external URLs,Forget rel="noopener","<UButton to=""https://example.com"" target=""_blank"">","<UButton href=""https://..."">",Low,https://ui.nuxt.com/docs/components/link
49,Loading,Use loadingAuto on buttons,Automatic loading state from @click promise,loadingAuto prop on UButton,Manual loading state,"<UButton loadingAuto @click=""async () => await save()"">","<UButton :loading=""isLoading"" @click=""save"">",Low,https://ui.nuxt.com/docs/components/button
50,Loading,Use UForm loadingAuto,Auto-disable form during submit,loadingAuto on UForm (default true),Manual form disabled state,"<UForm @submit=""handleSubmit"">","<UForm :disabled=""isSubmitting"">",Low,https://ui.nuxt.com/docs/components/form
.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Routing,Use file-based routing,Create routes by adding files in pages directory,pages/ directory with index.vue,Manual route configuration,pages/dashboard/index.vue,Custom router setup,Medium,https://nuxt.com/docs/getting-started/routing
2,Routing,Use dynamic route parameters,Create dynamic routes with bracket syntax,[id].vue for dynamic params,Hardcoded routes for dynamic content,pages/posts/[id].vue,pages/posts/post1.vue,Medium,https://nuxt.com/docs/getting-started/routing
3,Routing,Use catch-all routes,Handle multiple path segments with [...slug],[...slug].vue for catch-all,Multiple nested dynamic routes,pages/[...slug].vue,pages/[a]/[b]/[c].vue,Low,https://nuxt.com/docs/getting-started/routing
4,Routing,Define page metadata with definePageMeta,Set page-level configuration and middleware,definePageMeta for layout middleware title,Manual route meta configuration,"definePageMeta({ layout: 'admin', middleware: 'auth' })",router.beforeEach for page config,High,https://nuxt.com/docs/api/utils/define-page-meta
5,Routing,Use validate for route params,Validate dynamic route parameters before rendering,validate function in definePageMeta,Manual validation in setup,"definePageMeta({ validate: (route) => /^\d+$/.test(route.params.id) })",if (!valid) navigateTo('/404'),Medium,https://nuxt.com/docs/api/utils/define-page-meta
6,Rendering,Use SSR by default,Server-side rendering is enabled by default,Keep ssr: true (default),Disable SSR unnecessarily,ssr: true (default),ssr: false for all pages,High,https://nuxt.com/docs/guide/concepts/rendering
7,Rendering,Use .client suffix for client-only components,Mark components to render only on client,ComponentName.client.vue suffix,v-if with process.client check,Comments.client.vue,<div v-if="process.client"><Comments/></div>,Medium,https://nuxt.com/docs/guide/directory-structure/components
8,Rendering,Use .server suffix for server-only components,Mark components to render only on server,ComponentName.server.vue suffix,Manual server check,HeavyMarkdown.server.vue,v-if="process.server",Low,https://nuxt.com/docs/guide/directory-structure/components
9,DataFetching,Use useFetch for simple data fetching,Wrapper around useAsyncData for URL fetching,useFetch for API calls,$fetch in onMounted,"const { data } = await useFetch('/api/posts')","onMounted(async () => { data.value = await $fetch('/api/posts') })",High,https://nuxt.com/docs/api/composables/use-fetch
10,DataFetching,Use useAsyncData for complex fetching,Fine-grained control over async data,useAsyncData for CMS or custom fetching,useFetch for non-URL data sources,"const { data } = await useAsyncData('posts', () => cms.getPosts())","const { data } = await useFetch(() => cms.getPosts())",Medium,https://nuxt.com/docs/api/composables/use-async-data
11,DataFetching,Use $fetch for non-reactive requests,$fetch for event handlers and non-component code,$fetch in event handlers or server routes,useFetch in click handlers,"async function submit() { await $fetch('/api/submit', { method: 'POST' }) }","async function submit() { await useFetch('/api/submit') }",High,https://nuxt.com/docs/api/utils/dollarfetch
12,DataFetching,Use lazy option for non-blocking fetch,Defer data fetching for better initial load,lazy: true for below-fold content,Blocking fetch for non-critical data,"useFetch('/api/comments', { lazy: true })",await useFetch('/api/comments') for footer,Medium,https://nuxt.com/docs/api/composables/use-fetch
13,DataFetching,Use server option to control fetch location,Choose where data is fetched,server: false for client-only data,Server fetch for user-specific client data,"useFetch('/api/user-preferences', { server: false })",useFetch for localStorage-dependent data,Medium,https://nuxt.com/docs/api/composables/use-fetch
14,DataFetching,Use pick to reduce payload size,Select only needed fields from response,pick option for large responses,Fetching entire objects when few fields needed,"useFetch('/api/user', { pick: ['id', 'name'] })",useFetch('/api/user') then destructure,Low,https://nuxt.com/docs/api/composables/use-fetch
15,DataFetching,Use transform for data manipulation,Transform data before storing in state,transform option for data shaping,Manual transformation after fetch,"useFetch('/api/posts', { transform: (posts) => posts.map(p => p.title) })",const titles = data.value.map(p => p.title),Low,https://nuxt.com/docs/api/composables/use-fetch
16,DataFetching,Handle loading and error states,Always handle pending and error states,Check status pending error refs,Ignoring loading states,"<div v-if=""status === 'pending'"">Loading...</div>",No loading indicator,High,https://nuxt.com/docs/getting-started/data-fetching
17,Lifecycle,Avoid side effects in script setup root,Move side effects to lifecycle hooks,Side effects in onMounted,setInterval in root script setup,"onMounted(() => { interval = setInterval(...) })","<script setup>setInterval(...)</script>",High,https://nuxt.com/docs/guide/concepts/nuxt-lifecycle
18,Lifecycle,Use onMounted for DOM access,Access DOM only after component is mounted,onMounted for DOM manipulation,Direct DOM access in setup,"onMounted(() => { document.getElementById('el') })","<script setup>document.getElementById('el')</script>",High,https://nuxt.com/docs/api/composables/on-mounted
19,Lifecycle,Use nextTick for post-render access,Wait for DOM updates before accessing elements,await nextTick() after state changes,Immediate DOM access after state change,"count.value++; await nextTick(); el.value.focus()","count.value++; el.value.focus()",Medium,https://nuxt.com/docs/api/utils/next-tick
20,Lifecycle,Use onPrehydrate for pre-hydration logic,Run code before Nuxt hydrates the page,onPrehydrate for client setup,onMounted for hydration-critical code,"onPrehydrate(() => { console.log(window) })",onMounted for pre-hydration needs,Low,https://nuxt.com/docs/api/composables/on-prehydrate
21,Server,Use server/api for API routes,Create API endpoints in server/api directory,server/api/users.ts for /api/users,Manual Express setup,server/api/hello.ts -> /api/hello,app.get('/api/hello'),High,https://nuxt.com/docs/guide/directory-structure/server
22,Server,Use defineEventHandler for handlers,Define server route handlers,defineEventHandler for all handlers,export default function,"export default defineEventHandler((event) => { return { hello: 'world' } })","export default function(req, res) {}",High,https://nuxt.com/docs/guide/directory-structure/server
23,Server,Use server/routes for non-api routes,Routes without /api prefix,server/routes for custom paths,server/api for non-api routes,server/routes/sitemap.xml.ts,server/api/sitemap.xml.ts,Medium,https://nuxt.com/docs/guide/directory-structure/server
24,Server,Use getQuery and readBody for input,Access query params and request body,getQuery(event) readBody(event),Direct event access,"const { id } = getQuery(event)",event.node.req.query,Medium,https://nuxt.com/docs/guide/directory-structure/server
25,Server,Validate server input,Always validate input in server handlers,Zod or similar for validation,Trust client input,"const body = await readBody(event); schema.parse(body)",const body = await readBody(event),High,https://nuxt.com/docs/guide/directory-structure/server
26,State,Use useState for shared reactive state,SSR-friendly shared state across components,useState for cross-component state,ref for shared state,"const count = useState('count', () => 0)",const count = ref(0) in composable,High,https://nuxt.com/docs/api/composables/use-state
27,State,Use unique keys for useState,Prevent state conflicts with unique keys,Descriptive unique keys for each state,Generic or duplicate keys,"useState('user-preferences', () => ({}))",useState('data') in multiple places,Medium,https://nuxt.com/docs/api/composables/use-state
28,State,Use Pinia for complex state,Pinia for advanced state management,@pinia/nuxt for complex apps,Custom state management,useMainStore() with Pinia,Custom reactive store implementation,Medium,https://nuxt.com/docs/getting-started/state-management
29,State,Use callOnce for one-time async operations,Ensure async operations run only once,callOnce for store initialization,Direct await in component,"await callOnce(store.fetch)",await store.fetch() on every render,Medium,https://nuxt.com/docs/api/utils/call-once
30,SEO,Use useSeoMeta for SEO tags,Type-safe SEO meta tag management,useSeoMeta for meta tags,useHead for simple meta,"useSeoMeta({ title: 'Home', ogTitle: 'Home', description: '...' })","useHead({ meta: [{ name: 'description', content: '...' }] })",High,https://nuxt.com/docs/api/composables/use-seo-meta
31,SEO,Use reactive values in useSeoMeta,Dynamic SEO tags with refs or getters,Computed getters for dynamic values,Static values for dynamic content,"useSeoMeta({ title: () => post.value.title })","useSeoMeta({ title: post.value.title })",Medium,https://nuxt.com/docs/api/composables/use-seo-meta
32,SEO,Use useHead for non-meta head elements,Scripts styles links in head,useHead for scripts and links,useSeoMeta for scripts,"useHead({ script: [{ src: '/analytics.js' }] })","useSeoMeta({ script: '...' })",Medium,https://nuxt.com/docs/api/composables/use-head
33,SEO,Include OpenGraph tags,Add OG tags for social sharing,ogTitle ogDescription ogImage,Missing social preview,"useSeoMeta({ ogImage: '/og.png', twitterCard: 'summary_large_image' })",No OG configuration,Medium,https://nuxt.com/docs/api/composables/use-seo-meta
34,Middleware,Use defineNuxtRouteMiddleware,Define route middleware properly,defineNuxtRouteMiddleware wrapper,export default function,"export default defineNuxtRouteMiddleware((to, from) => {})","export default function(to, from) {}",High,https://nuxt.com/docs/guide/directory-structure/middleware
35,Middleware,Use navigateTo for redirects,Redirect in middleware with navigateTo,return navigateTo('/login'),router.push in middleware,"if (!auth) return navigateTo('/login')","if (!auth) router.push('/login')",High,https://nuxt.com/docs/api/utils/navigate-to
36,Middleware,Reference middleware in definePageMeta,Apply middleware to specific pages,middleware array in definePageMeta,Global middleware for page-specific,definePageMeta({ middleware: ['auth'] }),Global auth check for one page,Medium,https://nuxt.com/docs/guide/directory-structure/middleware
37,Middleware,Use .global suffix for global middleware,Apply middleware to all routes,auth.global.ts for app-wide auth,Manual middleware on every page,middleware/auth.global.ts,middleware: ['auth'] on every page,Medium,https://nuxt.com/docs/guide/directory-structure/middleware
38,ErrorHandling,Use createError for errors,Create errors with proper status codes,createError with statusCode,throw new Error,"throw createError({ statusCode: 404, statusMessage: 'Not Found' })",throw new Error('Not Found'),High,https://nuxt.com/docs/api/utils/create-error
39,ErrorHandling,Use NuxtErrorBoundary for local errors,Handle errors within component subtree,NuxtErrorBoundary for component errors,Global error page for local errors,"<NuxtErrorBoundary @error=""log""><template #error=""{ error }"">",error.vue for component errors,Medium,https://nuxt.com/docs/getting-started/error-handling
40,ErrorHandling,Use clearError to recover from errors,Clear error state and optionally redirect,clearError({ redirect: '/' }),Manual error state reset,clearError({ redirect: '/home' }),error.value = null,Medium,https://nuxt.com/docs/api/utils/clear-error
41,ErrorHandling,Use short statusMessage,Keep statusMessage brief for security,Short generic messages,Detailed error info in statusMessage,"createError({ statusCode: 400, statusMessage: 'Bad Request' })","createError({ statusMessage: 'Invalid user ID: 123' })",High,https://nuxt.com/docs/getting-started/error-handling
42,Link,Use NuxtLink for internal navigation,Client-side navigation with prefetching,<NuxtLink to> for internal links,<a href> for internal links,<NuxtLink to="/about">About</NuxtLink>,<a href="/about">About</a>,High,https://nuxt.com/docs/api/components/nuxt-link
43,Link,Configure prefetch behavior,Control when prefetching occurs,prefetchOn for interaction-based,Default prefetch for low-priority,"<NuxtLink prefetch-on=""interaction"">",Always default prefetch,Low,https://nuxt.com/docs/api/components/nuxt-link
44,Link,Use useRouter for programmatic navigation,Navigate programmatically,useRouter().push() for navigation,Direct window.location,"const router = useRouter(); router.push('/dashboard')",window.location.href = '/dashboard',Medium,https://nuxt.com/docs/api/composables/use-router
45,Link,Use navigateTo in composables,Navigate outside components,navigateTo() in middleware or plugins,useRouter in non-component code,return navigateTo('/login'),router.push in middleware,Medium,https://nuxt.com/docs/api/utils/navigate-to
46,AutoImports,Leverage auto-imports,Use auto-imported composables directly,Direct use of ref computed useFetch,Manual imports for Nuxt composables,"const count = ref(0)","import { ref } from 'vue'; const count = ref(0)",Medium,https://nuxt.com/docs/guide/concepts/auto-imports
47,AutoImports,Use #imports for explicit imports,Explicit imports when needed,#imports for clarity or disabled auto-imports,"import from 'vue' when auto-import enabled","import { ref } from '#imports'","import { ref } from 'vue'",Low,https://nuxt.com/docs/guide/concepts/auto-imports
48,AutoImports,Configure third-party auto-imports,Add external package auto-imports,imports.presets in nuxt.config,Manual imports everywhere,"imports: { presets: [{ from: 'vue-i18n', imports: ['useI18n'] }] }",import { useI18n } everywhere,Low,https://nuxt.com/docs/guide/concepts/auto-imports
49,Plugins,Use defineNuxtPlugin,Define plugins properly,defineNuxtPlugin wrapper,export default function,"export default defineNuxtPlugin((nuxtApp) => {})","export default function(ctx) {}",High,https://nuxt.com/docs/guide/directory-structure/plugins
50,Plugins,Use provide for injection,Provide helpers across app,return { provide: {} } for type safety,nuxtApp.provide without types,"return { provide: { hello: (name) => `Hello ${name}!` } }","nuxtApp.provide('hello', fn)",Medium,https://nuxt.com/docs/guide/directory-structure/plugins
51,Plugins,Use .client or .server suffix,Control plugin execution environment,plugin.client.ts for client-only,if (process.client) checks,analytics.client.ts,"if (process.client) { // analytics }",Medium,https://nuxt.com/docs/guide/directory-structure/plugins
52,Environment,Use runtimeConfig for env vars,Access environment variables safely,runtimeConfig in nuxt.config,process.env directly,"runtimeConfig: { apiSecret: '', public: { apiBase: '' } }",process.env.API_SECRET in components,High,https://nuxt.com/docs/guide/going-further/runtime-config
53,Environment,Use NUXT_ prefix for env override,Override config with environment variables,NUXT_API_SECRET NUXT_PUBLIC_API_BASE,Custom env var names,NUXT_PUBLIC_API_BASE=https://api.example.com,API_BASE=https://api.example.com,High,https://nuxt.com/docs/guide/going-further/runtime-config
54,Environment,Access public config with useRuntimeConfig,Get public config in components,useRuntimeConfig().public,Direct process.env access,const config = useRuntimeConfig(); config.public.apiBase,process.env.NUXT_PUBLIC_API_BASE,High,https://nuxt.com/docs/api/composables/use-runtime-config
55,Environment,Keep secrets in private config,Server-only secrets in runtimeConfig root,runtimeConfig.apiSecret (server only),Secrets in public config,runtimeConfig: { dbPassword: '' },runtimeConfig: { public: { dbPassword: '' } },High,https://nuxt.com/docs/guide/going-further/runtime-config
56,Performance,Use Lazy prefix for code splitting,Lazy load components with Lazy prefix,<LazyComponent> for below-fold,Eager load all components,<LazyMountainsList v-if="show"/>,<MountainsList/> for hidden content,Medium,https://nuxt.com/docs/guide/directory-structure/components
57,Performance,Use useLazyFetch for non-blocking data,Alias for useFetch with lazy: true,useLazyFetch for secondary data,useFetch for all requests,"const { data } = useLazyFetch('/api/comments')",await useFetch for comments section,Medium,https://nuxt.com/docs/api/composables/use-lazy-fetch
58,Performance,Use lazy hydration for interactivity,Delay component hydration until needed,LazyComponent with hydration strategy,Immediate hydration for all,<LazyModal hydrate-on-visible/>,<Modal/> in footer,Low,https://nuxt.com/docs/guide/going-further/experimental-features
.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Components,Use functional components,Hooks-based components are standard,Functional components with hooks,Class components,const App = () => { },class App extends Component,Medium,https://reactnative.dev/docs/intro-react
2,Components,Keep components small,Single responsibility principle,Split into smaller components,Large monolithic components,<Header /><Content /><Footer />,500+ line component,Medium,
3,Components,Use TypeScript,Type safety for props and state,TypeScript for new projects,JavaScript without types,const Button: FC<Props> = () => { },const Button = (props) => { },Medium,
4,Components,Colocate component files,Keep related files together,Component folder with styles,Flat structure,components/Button/index.tsx styles.ts,components/Button.tsx styles/button.ts,Low,
5,Styling,Use StyleSheet.create,Optimized style objects,StyleSheet for all styles,Inline style objects,StyleSheet.create({ container: {} }),style={{ margin: 10 }},High,https://reactnative.dev/docs/stylesheet
6,Styling,Avoid inline styles,Prevent object recreation,Styles in StyleSheet,Inline style objects in render,style={styles.container},"style={{ margin: 10, padding: 5 }}",Medium,
7,Styling,Use flexbox for layout,React Native uses flexbox,flexDirection alignItems justifyContent,Absolute positioning everywhere,flexDirection: 'row',position: 'absolute' everywhere,Medium,https://reactnative.dev/docs/flexbox
8,Styling,Handle platform differences,Platform-specific styles,Platform.select or .ios/.android files,Same styles for both platforms,"Platform.select({ ios: {}, android: {} })",Hardcoded iOS values,Medium,https://reactnative.dev/docs/platform-specific-code
9,Styling,Use responsive dimensions,Scale for different screens,Dimensions or useWindowDimensions,Fixed pixel values,useWindowDimensions(),width: 375,Medium,
10,Navigation,Use React Navigation,Standard navigation library,React Navigation for routing,Manual navigation management,createStackNavigator(),Custom navigation state,Medium,https://reactnavigation.org/
11,Navigation,Type navigation params,Type-safe navigation,Typed navigation props,Untyped navigation,"navigation.navigate<RootStackParamList>('Home', { id })","navigation.navigate('Home', { id })",Medium,
12,Navigation,Use deep linking,Support URL-based navigation,Configure linking prop,No deep link support,linking: { prefixes: [] },No linking configuration,Medium,https://reactnavigation.org/docs/deep-linking/
13,Navigation,Handle back button,Android back button handling,useFocusEffect with BackHandler,Ignore back button,BackHandler.addEventListener,No back handler,High,
14,State,Use useState for local state,Simple component state,useState for UI state,Class component state,"const [count, setCount] = useState(0)",this.state = { count: 0 },Medium,
15,State,Use useReducer for complex state,Complex state logic,useReducer for related state,Multiple useState for related values,useReducer(reducer initialState),5+ useState calls,Medium,
16,State,Use context sparingly,Context for global state,Context for theme auth locale,Context for frequently changing data,ThemeContext for app theme,Context for list item data,Medium,
17,State,Consider Zustand or Redux,External state management,Zustand for simple Redux for complex,useState for global state,create((set) => ({ })),Prop drilling global state,Medium,
18,Lists,Use FlatList for long lists,Virtualized list rendering,FlatList for 50+ items,ScrollView with map,<FlatList data={items} />,<ScrollView>{items.map()}</ScrollView>,High,https://reactnative.dev/docs/flatlist
19,Lists,Provide keyExtractor,Unique keys for list items,keyExtractor with stable ID,Index as key,keyExtractor={(item) => item.id},"keyExtractor={(_, index) => index}",High,
20,Lists,Optimize renderItem,Memoize list item components,React.memo for list items,Inline render function,renderItem={({ item }) => <MemoizedItem item={item} />},renderItem={({ item }) => <View>...</View>},High,
21,Lists,Use getItemLayout for fixed height,Skip measurement for performance,getItemLayout when height known,Dynamic measurement for fixed items,"getItemLayout={(_, index) => ({ length: 50, offset: 50 * index, index })}",No getItemLayout for fixed height,Medium,
22,Lists,Implement windowSize,Control render window,Smaller windowSize for memory,Default windowSize for large lists,windowSize={5},windowSize={21} for huge lists,Medium,
23,Performance,Use React.memo,Prevent unnecessary re-renders,memo for pure components,No memoization,export default memo(MyComponent),export default MyComponent,Medium,
24,Performance,Use useCallback for handlers,Stable function references,useCallback for props,New function on every render,"useCallback(() => {}, [deps])",() => handlePress(),Medium,
25,Performance,Use useMemo for expensive ops,Cache expensive calculations,useMemo for heavy computations,Recalculate every render,"useMemo(() => expensive(), [deps])",const result = expensive(),Medium,
26,Performance,Avoid anonymous functions in JSX,Prevent re-renders,Named handlers or useCallback,Inline arrow functions,onPress={handlePress},onPress={() => doSomething()},Medium,
27,Performance,Use Hermes engine,Improved startup and memory,Enable Hermes in build,JavaScriptCore for new projects,hermes_enabled: true,hermes_enabled: false,Medium,https://reactnative.dev/docs/hermes
28,Images,Use expo-image,Modern performant image component for React Native,"Use expo-image for caching, blurring, and performance",Use default Image for heavy lists or unmaintained libraries,<Image source={url} cachePolicy='memory-disk' /> (expo-image),<FastImage source={url} />,Medium,https://docs.expo.dev/versions/latest/sdk/image/
29,Images,Specify image dimensions,Prevent layout shifts,width and height for remote images,No dimensions for network images,<Image style={{ width: 100 height: 100 }} />,<Image source={{ uri }} /> no size,High,
30,Images,Use resizeMode,Control image scaling,resizeMode cover contain,Stretch images,"resizeMode=""cover""",No resizeMode,Low,
31,Forms,Use controlled inputs,State-controlled form fields,value + onChangeText,Uncontrolled inputs,<TextInput value={text} onChangeText={setText} />,<TextInput defaultValue={text} />,Medium,
32,Forms,Handle keyboard,Manage keyboard visibility,KeyboardAvoidingView,Content hidden by keyboard,"<KeyboardAvoidingView behavior=""padding"">",No keyboard handling,High,https://reactnative.dev/docs/keyboardavoidingview
33,Forms,Use proper keyboard types,Appropriate keyboard for input,keyboardType for input type,Default keyboard for all,"keyboardType=""email-address""","keyboardType=""default"" for email",Low,
34,Touch,Use Pressable,Modern touch handling,Pressable for touch interactions,TouchableOpacity for new code,<Pressable onPress={} />,<TouchableOpacity onPress={} />,Low,https://reactnative.dev/docs/pressable
35,Touch,Provide touch feedback,Visual feedback on press,Ripple or opacity change,No feedback on press,android_ripple={{ color: 'gray' }},No press feedback,Medium,
36,Touch,Set hitSlop for small targets,Increase touch area,hitSlop for icons and small buttons,Tiny touch targets,hitSlop={{ top: 10 bottom: 10 }},44x44 with no hitSlop,Medium,
37,Animation,Use Reanimated,High-performance animations,react-native-reanimated,Animated API for complex,useSharedValue useAnimatedStyle,Animated.timing for gesture,Medium,https://docs.swmansion.com/react-native-reanimated/
38,Animation,Run on UI thread,worklets for smooth animation,Run animations on UI thread,JS thread animations,runOnUI(() => {}),Animated on JS thread,High,
39,Animation,Use gesture handler,Native gesture recognition,react-native-gesture-handler,JS-based gesture handling,<GestureDetector>,<View onTouchMove={} />,Medium,https://docs.swmansion.com/react-native-gesture-handler/
40,Async,Handle loading states,Show loading indicators,ActivityIndicator during load,Empty screen during load,{isLoading ? <ActivityIndicator /> : <Content />},No loading state,Medium,
41,Async,Handle errors gracefully,Error boundaries and fallbacks,Error UI for failed requests,Crash on error,{error ? <ErrorView /> : <Content />},No error handling,High,
42,Async,Cancel async operations,Cleanup on unmount,AbortController or cleanup,Memory leaks from async,useEffect cleanup,No cleanup for subscriptions,High,
43,Accessibility,Add accessibility labels,Describe UI elements,accessibilityLabel for all interactive,Missing labels,"accessibilityLabel=""Submit form""",<Pressable> without label,High,https://reactnative.dev/docs/accessibility
44,Accessibility,Use accessibility roles,Semantic meaning,accessibilityRole for elements,Wrong roles,"accessibilityRole=""button""",No role for button,Medium,
45,Accessibility,Support screen readers,Test with TalkBack/VoiceOver,Test with screen readers,Skip accessibility testing,Regular TalkBack testing,No screen reader testing,High,
46,Testing,Use React Native Testing Library,Component testing,render and fireEvent,Enzyme or manual testing,render(<Component />),shallow(<Component />),Medium,https://callstack.github.io/react-native-testing-library/
47,Testing,Test on real devices,Real device behavior,Test on iOS and Android devices,Simulator only,Device testing in CI,Simulator only testing,High,
48,Testing,Use Detox for E2E,End-to-end testing,Detox for critical flows,Manual E2E testing,detox test,Manual testing only,Medium,https://wix.github.io/Detox/
49,Native,Use native modules carefully,Bridge has overhead,Batch native calls,Frequent bridge crossing,Batch updates,Call native on every keystroke,High,
50,Native,Use Expo when possible,Simplified development,Expo for standard features,Bare RN for simple apps,expo install package,react-native link package,Low,https://docs.expo.dev/
51,Native,Handle permissions,Request permissions properly,Check and request permissions,Assume permissions granted,PermissionsAndroid.request(),Access without permission check,High,https://reactnative.dev/docs/permissionsandroid
.claude/skills/ui-ux-pro-max/data/stacks/react.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,State,Use useState for local state,Simple component state should use useState hook,useState for form inputs toggles counters,Class components this.state,"const [count, setCount] = useState(0)",this.state = { count: 0 },Medium,https://react.dev/reference/react/useState
2,State,Lift state up when needed,Share state between siblings by lifting to parent,Lift shared state to common ancestor,Prop drilling through many levels,Parent holds state passes down,Deep prop chains,Medium,https://react.dev/learn/sharing-state-between-components
3,State,Use useReducer for complex state,Complex state logic benefits from reducer pattern,useReducer for state with multiple sub-values,Multiple useState for related values,useReducer with action types,5+ useState calls that update together,Medium,https://react.dev/reference/react/useReducer
4,State,Avoid unnecessary state,Derive values from existing state when possible,Compute derived values in render,Store derivable values in state,const total = items.reduce(...),"const [total, setTotal] = useState(0)",High,https://react.dev/learn/choosing-the-state-structure
5,State,Initialize state lazily,Use function form for expensive initial state,useState(() => computeExpensive()),useState(computeExpensive()),useState(() => JSON.parse(data)),useState(JSON.parse(data)),Medium,https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state
6,Effects,Clean up effects,Return cleanup function for subscriptions timers,Return cleanup function in useEffect,No cleanup for subscriptions,useEffect(() => { sub(); return unsub; }),useEffect(() => { subscribe(); }),High,https://react.dev/reference/react/useEffect#connecting-to-an-external-system
7,Effects,Specify dependencies correctly,Include all values used inside effect in deps array,All referenced values in dependency array,Empty deps with external references,[value] when using value in effect,[] when using props/state in effect,High,https://react.dev/reference/react/useEffect#specifying-reactive-dependencies
8,Effects,Avoid unnecessary effects,Don't use effects for transforming data or events,Transform data during render handle events directly,useEffect for derived state or event handling,const filtered = items.filter(...),useEffect(() => setFiltered(items.filter(...))),High,https://react.dev/learn/you-might-not-need-an-effect
9,Effects,Use refs for non-reactive values,Store values that don't trigger re-renders in refs,useRef for interval IDs DOM elements,useState for values that don't need render,const intervalRef = useRef(null),"const [intervalId, setIntervalId] = useState()",Medium,https://react.dev/reference/react/useRef
10,Rendering,Use keys properly,Stable unique keys for list items,Use stable IDs as keys,Array index as key for dynamic lists,key={item.id},key={index},High,https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key
11,Rendering,Memoize expensive calculations,Use useMemo for costly computations,useMemo for expensive filtering/sorting,Recalculate every render,"useMemo(() => expensive(), [deps])",const result = expensiveCalc(),Medium,https://react.dev/reference/react/useMemo
12,Rendering,Memoize callbacks passed to children,Use useCallback for functions passed as props,useCallback for handlers passed to memoized children,New function reference every render,"useCallback(() => {}, [deps])",const handler = () => {},Medium,https://react.dev/reference/react/useCallback
13,Rendering,Use React.memo wisely,Wrap components that render often with same props,memo for pure components with stable props,memo everything or nothing,memo(ExpensiveList),memo(SimpleButton),Low,https://react.dev/reference/react/memo
14,Rendering,Avoid inline object/array creation in JSX,Create objects outside render or memoize,Define style objects outside component,Inline objects in props,<div style={styles.container}>,<div style={{ margin: 10 }}>,Medium,
15,Components,Keep components small and focused,Single responsibility for each component,One concern per component,Large multi-purpose components,<UserAvatar /><UserName />,<UserCard /> with 500 lines,Medium,
16,Components,Use composition over inheritance,Compose components using children and props,Use children prop for flexibility,Inheritance hierarchies,<Card>{content}</Card>,class SpecialCard extends Card,Medium,https://react.dev/learn/thinking-in-react
17,Components,Colocate related code,Keep related components and hooks together,Related files in same directory,Flat structure with many files,components/User/UserCard.tsx,components/UserCard.tsx + hooks/useUser.ts,Low,
18,Components,Use fragments to avoid extra DOM,Fragment or <> for multiple elements without wrapper,<> for grouping without DOM node,Extra div wrappers,<>{items.map(...)}</>,<div>{items.map(...)}</div>,Low,https://react.dev/reference/react/Fragment
19,Props,Destructure props,Destructure props for cleaner component code,Destructure in function signature,props.name props.value throughout,"function User({ name, age })",function User(props),Low,
20,Props,Provide default props values,Use default parameters or defaultProps,Default values in destructuring,Undefined checks throughout,function Button({ size = 'md' }),if (size === undefined) size = 'md',Low,
21,Props,Avoid prop drilling,Use context or composition for deeply nested data,Context for global data composition for UI,Passing props through 5+ levels,<UserContext.Provider>,<A user={u}><B user={u}><C user={u}>,Medium,https://react.dev/learn/passing-data-deeply-with-context
22,Props,Validate props with TypeScript,Use TypeScript interfaces for prop types,interface Props { name: string },PropTypes or no validation,interface ButtonProps { onClick: () => void },Button.propTypes = {},Medium,
23,Events,Use synthetic events correctly,React normalizes events across browsers,e.preventDefault() e.stopPropagation(),Access native event unnecessarily,onClick={(e) => e.preventDefault()},onClick={(e) => e.nativeEvent.preventDefault()},Low,https://react.dev/reference/react-dom/components/common#react-event-object
24,Events,Avoid binding in render,Use arrow functions in class or hooks,Arrow functions in functional components,bind in render or constructor,const handleClick = () => {},this.handleClick.bind(this),Medium,
25,Events,Pass event handlers not call results,Pass function reference not invocation,onClick={handleClick},onClick={handleClick()} causing immediate call,onClick={handleClick},onClick={handleClick()},High,
26,Forms,Controlled components for forms,Use state to control form inputs,value + onChange for inputs,Uncontrolled inputs with refs,<input value={val} onChange={setVal}>,<input ref={inputRef}>,Medium,https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable
27,Forms,Handle form submission properly,Prevent default and handle in submit handler,onSubmit with preventDefault,onClick on submit button only,<form onSubmit={handleSubmit}>,<button onClick={handleSubmit}>,Medium,
28,Forms,Debounce rapid input changes,Debounce search/filter inputs,useDeferredValue or debounce for search,Filter on every keystroke,useDeferredValue(searchTerm),useEffect filtering on every change,Medium,https://react.dev/reference/react/useDeferredValue
29,Hooks,Follow rules of hooks,Only call hooks at top level and in React functions,Hooks at component top level,Hooks in conditions loops or callbacks,"const [x, setX] = useState()","if (cond) { const [x, setX] = useState() }",High,https://react.dev/reference/rules/rules-of-hooks
30,Hooks,Custom hooks for reusable logic,Extract shared stateful logic to custom hooks,useCustomHook for reusable patterns,Duplicate hook logic across components,const { data } = useFetch(url),Duplicate useEffect/useState in components,Medium,https://react.dev/learn/reusing-logic-with-custom-hooks
31,Hooks,Name custom hooks with use prefix,Custom hooks must start with use,useFetch useForm useAuth,fetchData or getData for hook,function useFetch(url),function fetchData(url),High,
32,Context,Use context for global data,Context for theme auth locale,Context for app-wide state,Context for frequently changing data,<ThemeContext.Provider>,Context for form field values,Medium,https://react.dev/learn/passing-data-deeply-with-context
33,Context,Split contexts by concern,Separate contexts for different domains,ThemeContext + AuthContext,One giant AppContext,<ThemeProvider><AuthProvider>,<AppProvider value={{theme user...}}>,Medium,
34,Context,Memoize context values,Prevent unnecessary re-renders with useMemo,useMemo for context value object,New object reference every render,"value={useMemo(() => ({...}), [])}","value={{ user, theme }}",High,
35,Performance,Use React DevTools Profiler,Profile to identify performance bottlenecks,Profile before optimizing,Optimize without measuring,React DevTools Profiler,Guessing at bottlenecks,Medium,https://react.dev/learn/react-developer-tools
36,Performance,Lazy load components,Use React.lazy for code splitting,lazy() for routes and heavy components,Import everything upfront,const Page = lazy(() => import('./Page')),import Page from './Page',Medium,https://react.dev/reference/react/lazy
37,Performance,Virtualize long lists,Use windowing for lists over 100 items,react-window or react-virtual,Render thousands of DOM nodes,<VirtualizedList items={items}/>,{items.map(i => <Item />)},High,
38,Performance,Batch state updates,React 18 auto-batches but be aware,Let React batch related updates,Manual batching with flushSync,setA(1); setB(2); // batched,flushSync(() => setA(1)),Low,https://react.dev/learn/queueing-a-series-of-state-updates
39,ErrorHandling,Use error boundaries,Catch JavaScript errors in component tree,ErrorBoundary wrapping sections,Let errors crash entire app,<ErrorBoundary><App/></ErrorBoundary>,No error handling,High,https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
40,ErrorHandling,Handle async errors,Catch errors in async operations,try/catch in async handlers,Unhandled promise rejections,try { await fetch() } catch(e) {},await fetch() // no catch,High,
41,Testing,Test behavior not implementation,Test what user sees and does,Test renders and interactions,Test internal state or methods,expect(screen.getByText('Hello')),expect(component.state.name),Medium,https://testing-library.com/docs/react-testing-library/intro/
42,Testing,Use testing-library queries,Use accessible queries,getByRole getByLabelText,getByTestId for everything,getByRole('button'),getByTestId('submit-btn'),Medium,https://testing-library.com/docs/queries/about#priority
43,Accessibility,Use semantic HTML,Proper HTML elements for their purpose,button for clicks nav for navigation,div with onClick for buttons,<button onClick={...}>,<div onClick={...}>,High,https://react.dev/reference/react-dom/components#all-html-components
44,Accessibility,Manage focus properly,Handle focus for modals dialogs,Focus trap in modals return focus on close,No focus management,useEffect to focus input,Modal without focus trap,High,
45,Accessibility,Announce dynamic content,Use ARIA live regions for updates,aria-live for dynamic updates,Silent updates to screen readers,"<div aria-live=""polite"">{msg}</div>",<div>{msg}</div>,Medium,
46,Accessibility,Label form controls,Associate labels with inputs,htmlFor matching input id,Placeholder as only label,"<label htmlFor=""email"">Email</label>","<input placeholder=""Email""/>",High,
47,TypeScript,Type component props,Define interfaces for all props,interface Props with all prop types,any or missing types,interface Props { name: string },function Component(props: any),High,
48,TypeScript,Type state properly,Provide types for useState,useState<Type>() for complex state,Inferred any types,useState<User | null>(null),useState(null),Medium,
49,TypeScript,Type event handlers,Use React event types,React.ChangeEvent<HTMLInputElement>,Generic Event type,onChange: React.ChangeEvent<HTMLInputElement>,onChange: Event,Medium,
50,TypeScript,Use generics for reusable components,Generic components for flexible typing,Generic props for list components,Union types for flexibility,<List<T> items={T[]}>,<List items={any[]}>,Medium,
51,Patterns,Container/Presentational split,Separate data logic from UI,Container fetches presentational renders,Mixed data and UI in one,<UserContainer><UserView/></UserContainer>,<User /> with fetch and render,Low,
52,Patterns,Render props for flexibility,Share code via render prop pattern,Render prop for customizable rendering,Duplicate logic across components,<DataFetcher render={data => ...}/>,Copy paste fetch logic,Low,https://react.dev/reference/react/cloneElement#passing-data-with-a-render-prop
53,Patterns,Compound components,Related components sharing state,Tab + TabPanel sharing context,Prop drilling between related,<Tabs><Tab/><TabPanel/></Tabs>,<Tabs tabs={[]} panels={[...]}/>,Low,
.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Setup,Use CLI for installation,Install components via shadcn CLI for proper setup,npx shadcn@latest add component-name,Manual copy-paste from docs,npx shadcn@latest add button,Copy component code manually,High,https://ui.shadcn.com/docs/cli
2,Setup,Initialize project properly,Run init command to set up components.json and globals.css,npx shadcn@latest init before adding components,Skip init and add components directly,npx shadcn@latest init,npx shadcn@latest add button (without init),High,https://ui.shadcn.com/docs/installation
3,Setup,Configure path aliases,Set up proper import aliases in tsconfig and components.json,Use @/components/ui path aliases,Relative imports like ../../components,import { Button } from "@/components/ui/button",import { Button } from "../../components/ui/button",Medium,https://ui.shadcn.com/docs/installation
4,Theming,Use CSS variables for colors,Define colors as CSS variables in globals.css for theming,CSS variables in :root and .dark,Hardcoded color values in components,bg-primary text-primary-foreground,bg-blue-500 text-white,High,https://ui.shadcn.com/docs/theming
5,Theming,Follow naming convention,Use semantic color names with foreground pattern,primary/primary-foreground secondary/secondary-foreground,Generic color names,--primary --primary-foreground,--blue --light-blue,Medium,https://ui.shadcn.com/docs/theming
6,Theming,Support dark mode,Include .dark class styles for all custom CSS,Define both :root and .dark color schemes,Only light mode colors,.dark { --background: 240 10% 3.9%; },No .dark class styles,High,https://ui.shadcn.com/docs/dark-mode
7,Components,Use component variants,Leverage cva variants for consistent styling,Use variant prop for different styles,Inline conditional classes,<Button variant="destructive">,<Button className={isError ? "bg-red-500" : "bg-blue-500"}>,Medium,https://ui.shadcn.com/docs/components/button
8,Components,Compose with className,Add custom classes via className prop for overrides,Extend with className for one-off customizations,Modify component source directly,<Button className="w-full">,Edit button.tsx to add w-full,Medium,https://ui.shadcn.com/docs/components/button
9,Components,Use size variants consistently,Apply size prop for consistent sizing across components,size="sm" size="lg" for sizing,Mix size classes inconsistently,<Button size="lg">,<Button className="text-lg px-8 py-4">,Medium,https://ui.shadcn.com/docs/components/button
10,Components,Prefer compound components,Use provided sub-components for complex UI,Card + CardHeader + CardContent pattern,Single component with many props,<Card><CardHeader><CardTitle>,<Card title="x" content="y" footer="z">,Medium,https://ui.shadcn.com/docs/components/card
11,Dialog,Use Dialog for modal content,Dialog component for overlay modal windows,Dialog for confirmations forms details,Alert for modal content,<Dialog><DialogContent>,<Alert> styled as modal,High,https://ui.shadcn.com/docs/components/dialog
12,Dialog,Handle dialog state properly,Use open and onOpenChange for controlled dialogs,Controlled state with useState,Uncontrolled with default open only,"<Dialog open={open} onOpenChange={setOpen}>","<Dialog defaultOpen={true}>",Medium,https://ui.shadcn.com/docs/components/dialog
13,Dialog,Include proper dialog structure,Use DialogHeader DialogTitle DialogDescription,Complete semantic structure,Missing title or description,<DialogHeader><DialogTitle><DialogDescription>,<DialogContent><p>Content</p></DialogContent>,High,https://ui.shadcn.com/docs/components/dialog
14,Sheet,Use Sheet for side panels,Sheet component for slide-out panels and drawers,Sheet for navigation filters settings,Dialog for side content,<Sheet side="right">,<Dialog> with slide animation,Medium,https://ui.shadcn.com/docs/components/sheet
15,Sheet,Specify sheet side,Set side prop for sheet slide direction,Explicit side="left" or side="right",Default side without consideration,<Sheet><SheetContent side="left">,<Sheet><SheetContent>,Low,https://ui.shadcn.com/docs/components/sheet
16,Form,Use Form with react-hook-form,Integrate Form component with react-hook-form for validation,useForm + Form + FormField pattern,Custom form handling without Form,<Form {...form}><FormField control={form.control}>,<form onSubmit={handleSubmit}>,High,https://ui.shadcn.com/docs/components/form
17,Form,Use FormField for inputs,Wrap inputs in FormField for proper labeling and errors,FormField + FormItem + FormLabel + FormControl,Input without FormField wrapper,<FormField><FormItem><FormLabel><FormControl><Input>,<Input onChange={...}>,High,https://ui.shadcn.com/docs/components/form
18,Form,Display form messages,Use FormMessage for validation error display,FormMessage after FormControl,Custom error text without FormMessage,<FormControl><Input/></FormControl><FormMessage/>,<Input/>{error && <span>{error}</span>},Medium,https://ui.shadcn.com/docs/components/form
19,Form,Use Zod for validation,Define form schema with Zod for type-safe validation,zodResolver with form schema,Manual validation logic,zodResolver(formSchema),validate: (values) => { if (!values.email) },Medium,https://ui.shadcn.com/docs/components/form
20,Select,Use Select for dropdowns,Select component for option selection,Select for choosing from list,Native select element,<Select><SelectTrigger><SelectContent>,<select><option>,Medium,https://ui.shadcn.com/docs/components/select
21,Select,Structure Select properly,Include Trigger Value Content and Items,Complete Select structure,Missing SelectValue or SelectContent,<SelectTrigger><SelectValue/></SelectTrigger><SelectContent><SelectItem>,<Select><option>,High,https://ui.shadcn.com/docs/components/select
22,Command,Use Command for search,Command component for searchable lists and palettes,Command for command palette search,Input with custom dropdown,<Command><CommandInput><CommandList>,<Input><div className="dropdown">,Medium,https://ui.shadcn.com/docs/components/command
23,Command,Group command items,Use CommandGroup for categorized items,CommandGroup with heading for sections,Flat list without grouping,<CommandGroup heading="Suggestions"><CommandItem>,<CommandItem> without groups,Low,https://ui.shadcn.com/docs/components/command
24,Table,Use Table for data display,Table component for structured data,Table for tabular data display,Div grid for table-like layouts,<Table><TableHeader><TableBody><TableRow>,<div className="grid">,Medium,https://ui.shadcn.com/docs/components/table
25,Table,Include proper table structure,Use TableHeader TableBody TableRow TableCell,Semantic table structure,Missing thead or tbody,<TableHeader><TableRow><TableHead>,<Table><TableRow> without header,High,https://ui.shadcn.com/docs/components/table
26,DataTable,Use DataTable for complex tables,Combine Table with TanStack Table for features,DataTable pattern for sorting filtering pagination,Custom table implementation,useReactTable + Table components,Custom sort filter pagination logic,Medium,https://ui.shadcn.com/docs/components/data-table
27,Tabs,Use Tabs for content switching,Tabs component for tabbed interfaces,Tabs for related content sections,Custom tab implementation,<Tabs><TabsList><TabsTrigger><TabsContent>,<div onClick={() => setTab(...)},Medium,https://ui.shadcn.com/docs/components/tabs
28,Tabs,Set default tab value,Specify defaultValue for initial tab,defaultValue on Tabs component,No default leaving first tab,<Tabs defaultValue="account">,<Tabs> without defaultValue,Low,https://ui.shadcn.com/docs/components/tabs
29,Accordion,Use Accordion for collapsible,Accordion for expandable content sections,Accordion for FAQ settings panels,Custom collapse implementation,<Accordion><AccordionItem><AccordionTrigger>,<div onClick={() => setOpen(!open)}>,Medium,https://ui.shadcn.com/docs/components/accordion
30,Accordion,Choose accordion type,Use type="single" or type="multiple" appropriately,type="single" for one open type="multiple" for many,Default type without consideration,<Accordion type="single" collapsible>,<Accordion> without type,Low,https://ui.shadcn.com/docs/components/accordion
31,Toast,Use Sonner for toasts,Sonner integration for toast notifications,toast() from sonner for notifications,Custom toast implementation,toast("Event created"),setShowToast(true),Medium,https://ui.shadcn.com/docs/components/sonner
32,Toast,Add Toaster to layout,Include Toaster component in root layout,<Toaster /> in app layout,Toaster in individual pages,app/layout.tsx: <Toaster />,page.tsx: <Toaster />,High,https://ui.shadcn.com/docs/components/sonner
33,Toast,Use toast variants,Apply toast.success toast.error for context,Semantic toast methods,Generic toast for all messages,toast.success("Saved!") toast.error("Failed"),toast("Saved!") toast("Failed"),Medium,https://ui.shadcn.com/docs/components/sonner
34,Popover,Use Popover for floating content,Popover for dropdown menus and floating panels,Popover for contextual actions,Absolute positioned divs,<Popover><PopoverTrigger><PopoverContent>,<div className="relative"><div className="absolute">,Medium,https://ui.shadcn.com/docs/components/popover
35,Popover,Handle popover alignment,Use align and side props for positioning,Explicit alignment configuration,Default alignment for all,<PopoverContent align="start" side="bottom">,<PopoverContent>,Low,https://ui.shadcn.com/docs/components/popover
36,DropdownMenu,Use DropdownMenu for actions,DropdownMenu for action lists and context menus,DropdownMenu for user menu actions,Popover for action lists,<DropdownMenu><DropdownMenuTrigger><DropdownMenuContent>,<Popover> for menu actions,Medium,https://ui.shadcn.com/docs/components/dropdown-menu
37,DropdownMenu,Group menu items,Use DropdownMenuGroup and DropdownMenuSeparator,Organized menu with separators,Flat list of items,<DropdownMenuGroup><DropdownMenuItem><DropdownMenuSeparator>,<DropdownMenuItem> without organization,Low,https://ui.shadcn.com/docs/components/dropdown-menu
38,Tooltip,Use Tooltip for hints,Tooltip for icon buttons and truncated text,Tooltip for additional context,Title attribute for tooltips,<Tooltip><TooltipTrigger><TooltipContent>,<button title="Delete">,Medium,https://ui.shadcn.com/docs/components/tooltip
39,Tooltip,Add TooltipProvider,Wrap app or section in TooltipProvider,TooltipProvider at app level,TooltipProvider per tooltip,<TooltipProvider><App/></TooltipProvider>,<Tooltip><TooltipProvider>,High,https://ui.shadcn.com/docs/components/tooltip
40,Skeleton,Use Skeleton for loading,Skeleton component for loading placeholders,Skeleton matching content layout,Spinner for content loading,<Skeleton className="h-4 w-[200px]"/>,<Spinner/> for card loading,Medium,https://ui.shadcn.com/docs/components/skeleton
41,Skeleton,Match skeleton dimensions,Size skeleton to match loaded content,Skeleton same size as expected content,Generic skeleton size,<Skeleton className="h-12 w-12 rounded-full"/>,<Skeleton/> without sizing,Medium,https://ui.shadcn.com/docs/components/skeleton
42,AlertDialog,Use AlertDialog for confirms,AlertDialog for destructive action confirmation,AlertDialog for delete confirmations,Dialog for confirmations,<AlertDialog><AlertDialogTrigger><AlertDialogContent>,<Dialog> for delete confirmation,High,https://ui.shadcn.com/docs/components/alert-dialog
43,AlertDialog,Include action buttons,Use AlertDialogAction and AlertDialogCancel,Standard confirm/cancel pattern,Custom buttons in AlertDialog,<AlertDialogCancel>Cancel</AlertDialogCancel><AlertDialogAction>,<Button>Cancel</Button><Button>Confirm</Button>,Medium,https://ui.shadcn.com/docs/components/alert-dialog
44,Sidebar,Use Sidebar for navigation,Sidebar component for app navigation,Sidebar for main app navigation,Custom sidebar implementation,<SidebarProvider><Sidebar><SidebarContent>,<div className="w-64 fixed">,Medium,https://ui.shadcn.com/docs/components/sidebar
45,Sidebar,Wrap in SidebarProvider,Use SidebarProvider for sidebar state management,SidebarProvider at layout level,Sidebar without provider,<SidebarProvider><Sidebar></SidebarProvider>,<Sidebar> without provider,High,https://ui.shadcn.com/docs/components/sidebar
46,Sidebar,Use SidebarTrigger,Include SidebarTrigger for mobile toggle,SidebarTrigger for responsive toggle,Custom toggle button,<SidebarTrigger/>,<Button onClick={() => toggleSidebar()}>,Medium,https://ui.shadcn.com/docs/components/sidebar
47,Chart,Use Chart for data viz,Chart component with Recharts integration,Chart component for dashboards,Direct Recharts without wrapper,<ChartContainer config={chartConfig}>,<ResponsiveContainer><BarChart>,Medium,https://ui.shadcn.com/docs/components/chart
48,Chart,Define chart config,Create chartConfig for consistent theming,chartConfig with color definitions,Inline colors in charts,"{ desktop: { label: ""Desktop"", color: ""#2563eb"" } }",<Bar fill="#2563eb"/>,Medium,https://ui.shadcn.com/docs/components/chart
49,Chart,Use ChartTooltip,Apply ChartTooltip for interactive charts,ChartTooltip with ChartTooltipContent,Recharts Tooltip directly,<ChartTooltip content={<ChartTooltipContent/>}/>,<Tooltip/> from recharts,Low,https://ui.shadcn.com/docs/components/chart
50,Blocks,Use blocks for scaffolding,Start from shadcn blocks for common layouts,npx shadcn@latest add dashboard-01,Build dashboard from scratch,npx shadcn@latest add login-01,Custom login page from scratch,Medium,https://ui.shadcn.com/blocks
51,Blocks,Customize block components,Modify copied block code to fit needs,Edit block files after installation,Use blocks without modification,Customize dashboard-01 layout,Use dashboard-01 as-is,Low,https://ui.shadcn.com/blocks
52,A11y,Use semantic components,Shadcn components have built-in ARIA,Rely on component accessibility,Override ARIA attributes,<Button> has button role,<div role="button">,High,https://ui.shadcn.com/docs/components/button
53,A11y,Maintain focus management,Dialog Sheet handle focus automatically,Let components manage focus,Custom focus handling,<Dialog> traps focus,document.querySelector().focus(),High,https://ui.shadcn.com/docs/components/dialog
54,A11y,Provide labels,Use FormLabel and aria-label appropriately,FormLabel for form inputs,Placeholder as only label,<FormLabel>Email</FormLabel><Input/>,<Input placeholder="Email"/>,High,https://ui.shadcn.com/docs/components/form
55,Performance,Import components individually,Import only needed components,Named imports from component files,Import all from index,import { Button } from "@/components/ui/button",import { Button Card Dialog } from "@/components/ui",Medium,
56,Performance,Lazy load dialogs,Dynamic import for heavy dialog content,React.lazy for dialog content,Import all dialogs upfront,const HeavyContent = lazy(() => import('./Heavy')),import HeavyContent from './Heavy',Medium,
57,Customization,Extend variants with cva,Add new variants using class-variance-authority,Extend buttonVariants for new styles,Inline classes for variants,"variants: { size: { xl: ""h-14 px-8"" } }",className="h-14 px-8",Medium,https://ui.shadcn.com/docs/components/button
58,Customization,Create custom components,Build new components following shadcn patterns,Use cn() and cva for custom components,Different patterns for custom,const Custom = ({ className }) => <div className={cn("base" className)}>,const Custom = ({ style }) => <div style={style}>,Medium,
59,Patterns,Use asChild for composition,asChild prop for component composition,Slot pattern with asChild,Wrapper divs for composition,<Button asChild><Link href="/">,<Button><Link href="/"></Link></Button>,Medium,https://ui.shadcn.com/docs/components/button
60,Patterns,Combine with React Hook Form,Form + useForm for complete forms,RHF Controller with shadcn inputs,Custom form state management,<FormField control={form.control} name="email">,<Input value={email} onChange={(e) => setEmail(e.target.value)},High,https://ui.shadcn.com/docs/components/form
.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Reactivity,Use $: for reactive statements,Automatic dependency tracking,$: for derived values,Manual recalculation,$: doubled = count * 2,let doubled; count && (doubled = count * 2),Medium,https://svelte.dev/docs/svelte-components#script-3-$-marks-a-statement-as-reactive
2,Reactivity,Trigger reactivity with assignment,Svelte tracks assignments not mutations,Reassign arrays/objects to trigger update,Mutate without reassignment,"items = [...items, newItem]",items.push(newItem),High,https://svelte.dev/docs/svelte-components#script-2-assignments-are-reactive
3,Reactivity,Use $state in Svelte 5,Runes for explicit reactivity,let count = $state(0),Implicit reactivity in Svelte 5,let count = $state(0),let count = 0 (Svelte 5),Medium,https://svelte.dev/blog/runes
4,Reactivity,Use $derived for computed values,$derived replaces $: in Svelte 5,let doubled = $derived(count * 2),$: in Svelte 5,let doubled = $derived(count * 2),$: doubled = count * 2 (Svelte 5),Medium,
5,Reactivity,Use $effect for side effects,$effect replaces $: side effects,Use $effect for subscriptions,$: for side effects in Svelte 5,$effect(() => console.log(count)),$: console.log(count) (Svelte 5),Medium,
6,Props,Export let for props,Declare props with export let,export let propName,Props without export,export let count = 0,let count = 0,High,https://svelte.dev/docs/svelte-components#script-1-export-creates-a-component-prop
7,Props,Use $props in Svelte 5,$props rune for prop access,let { name } = $props(),export let in Svelte 5,"let { name, age = 0 } = $props()",export let name; export let age = 0,Medium,
8,Props,Provide default values,Default props with assignment,export let count = 0,Required props without defaults,export let count = 0,export let count,Low,
9,Props,Use spread props,Pass through unknown props,{...$$restProps} on elements,Manual prop forwarding,<button {...$$restProps}>,<button class={$$props.class}>,Low,https://svelte.dev/docs/basic-markup#attributes-and-props
10,Bindings,Use bind: for two-way binding,Simplified input handling,bind:value for inputs,on:input with manual update,<input bind:value={name}>,<input value={name} on:input={e => name = e.target.value}>,Low,https://svelte.dev/docs/element-directives#bind-property
11,Bindings,Bind to DOM elements,Reference DOM nodes,bind:this for element reference,querySelector in onMount,<div bind:this={el}>,onMount(() => el = document.querySelector()),Medium,
12,Bindings,Use bind:group for radios/checkboxes,Simplified group handling,bind:group for radio/checkbox groups,Manual checked handling,"<input type=""radio"" bind:group={selected}>","<input type=""radio"" checked={selected === value}>",Low,
13,Events,Use on: for event handlers,Event directive syntax,on:click={handler},addEventListener in onMount,<button on:click={handleClick}>,onMount(() => btn.addEventListener()),Medium,https://svelte.dev/docs/element-directives#on-eventname
14,Events,Forward events with on:event,Pass events to parent,on:click without handler,createEventDispatcher for DOM events,<button on:click>,"dispatch('click', event)",Low,
15,Events,Use createEventDispatcher,Custom component events,dispatch for custom events,on:event for custom events,"dispatch('save', { data })",on:save without dispatch,Medium,https://svelte.dev/docs/svelte#createeventdispatcher
16,Lifecycle,Use onMount for initialization,Run code after component mounts,onMount for setup and data fetching,Code in script body for side effects,onMount(() => fetchData()),fetchData() in script body,High,https://svelte.dev/docs/svelte#onmount
17,Lifecycle,Return cleanup from onMount,Automatic cleanup on destroy,Return function from onMount,Separate onDestroy for paired cleanup,onMount(() => { sub(); return unsub }),onMount(sub); onDestroy(unsub),Medium,
18,Lifecycle,Use onDestroy sparingly,Only when onMount cleanup not possible,onDestroy for non-mount cleanup,onDestroy for mount-related cleanup,onDestroy for store unsubscribe,onDestroy(() => clearInterval(id)),Low,
19,Lifecycle,Avoid beforeUpdate/afterUpdate,Usually not needed,Reactive statements instead,beforeUpdate for derived state,$: if (x) doSomething(),beforeUpdate(() => doSomething()),Low,
20,Stores,Use writable for mutable state,Basic reactive store,writable for shared mutable state,Local variables for shared state,const count = writable(0),let count = 0 in module,Medium,https://svelte.dev/docs/svelte-store#writable
21,Stores,Use readable for read-only state,External data sources,readable for derived/external data,writable for read-only data,"readable(0, set => interval(set))",writable(0) for timer,Low,https://svelte.dev/docs/svelte-store#readable
22,Stores,Use derived for computed stores,Combine or transform stores,derived for computed values,Manual subscription for derived,"derived(count, $c => $c * 2)",count.subscribe(c => doubled = c * 2),Medium,https://svelte.dev/docs/svelte-store#derived
23,Stores,Use $ prefix for auto-subscription,Automatic subscribe/unsubscribe,$storeName in components,Manual subscription,{$count},count.subscribe(c => value = c),High,
24,Stores,Clean up custom subscriptions,Unsubscribe when component destroys,Return unsubscribe from onMount,Leave subscriptions open,onMount(() => store.subscribe(fn)),store.subscribe(fn) in script,High,
25,Slots,Use slots for composition,Content projection,<slot> for flexible content,Props for all content,<slot>Default</slot>,"<Component content=""text""/>",Medium,https://svelte.dev/docs/special-elements#slot
26,Slots,Name slots for multiple areas,Multiple content areas,"<slot name=""header"">",Single slot for complex layouts,"<slot name=""header""><slot name=""footer"">",<slot> with complex conditionals,Low,
27,Slots,Check slot content with $$slots,Conditional slot rendering,$$slots.name for conditional rendering,Always render slot wrapper,"{#if $$slots.footer}<slot name=""footer""/>{/if}","<div><slot name=""footer""/></div>",Low,
28,Styling,Use scoped styles by default,Styles scoped to component,<style> for component styles,Global styles for component,:global() only when needed,<style> all global,Medium,https://svelte.dev/docs/svelte-components#style
29,Styling,Use :global() sparingly,Escape scoping when needed,:global for third-party styling,Global for all styles,:global(.external-lib),<style> without scoping,Medium,
30,Styling,Use CSS variables for theming,Dynamic styling,CSS custom properties,Inline styles for themes,"style=""--color: {color}""","style=""color: {color}""",Low,
31,Transitions,Use built-in transitions,Svelte transition directives,transition:fade for simple effects,Manual CSS transitions,<div transition:fade>,<div class:fade={visible}>,Low,https://svelte.dev/docs/element-directives#transition-fn
32,Transitions,Use in: and out: separately,Different enter/exit animations,in:fly out:fade for asymmetric,Same transition for both,<div in:fly out:fade>,<div transition:fly>,Low,
33,Transitions,Add local modifier,Prevent ancestor trigger,transition:fade|local,Global transitions for lists,<div transition:slide|local>,<div transition:slide>,Medium,
34,Actions,Use actions for DOM behavior,Reusable DOM logic,use:action for DOM enhancements,onMount for each usage,<div use:clickOutside>,onMount(() => setupClickOutside(el)),Medium,https://svelte.dev/docs/element-directives#use-action
35,Actions,Return update and destroy,Lifecycle methods for actions,"Return { update, destroy }",Only initial setup,"return { update(params) {}, destroy() {} }",return destroy only,Medium,
36,Actions,Pass parameters to actions,Configure action behavior,use:action={params},Hardcoded action behavior,<div use:tooltip={options}>,<div use:tooltip>,Low,
37,Logic,Use {#if} for conditionals,Template conditionals,{#if} {:else if} {:else},Ternary in expressions,{#if cond}...{:else}...{/if},{cond ? a : b} for complex,Low,https://svelte.dev/docs/logic-blocks#if
38,Logic,Use {#each} for lists,List rendering,{#each} with key,Map in expression,{#each items as item (item.id)},{items.map(i => `<div>${i}</div>`)},Medium,
39,Logic,Always use keys in {#each},Proper list reconciliation,(item.id) for unique key,Index as key or no key,{#each items as item (item.id)},"{#each items as item, i (i)}",High,
40,Logic,Use {#await} for promises,Handle async states,{#await} for loading/error states,Manual promise handling,{#await promise}...{:then}...{:catch},{#if loading}...{#if error},Medium,https://svelte.dev/docs/logic-blocks#await
41,SvelteKit,Use +page.svelte for routes,File-based routing,+page.svelte for route components,Custom routing setup,routes/about/+page.svelte,routes/About.svelte,Medium,https://kit.svelte.dev/docs/routing
42,SvelteKit,Use +page.js for data loading,Load data before render,load function in +page.js,onMount for data fetching,export function load() {},onMount(() => fetchData()),High,https://kit.svelte.dev/docs/load
43,SvelteKit,Use +page.server.js for server-only,Server-side data loading,+page.server.js for sensitive data,+page.js for API keys,+page.server.js with DB access,+page.js with DB access,High,
44,SvelteKit,Use form actions,Server-side form handling,+page.server.js actions,API routes for forms,export const actions = { default },fetch('/api/submit'),Medium,https://kit.svelte.dev/docs/form-actions
45,SvelteKit,Use $app/stores for app state,$page $navigating $updated,$page for current page data,Manual URL parsing,import { page } from '$app/stores',window.location.pathname,Medium,https://kit.svelte.dev/docs/modules#$app-stores
46,Performance,Use {#key} for forced re-render,Reset component state,{#key id} for fresh instance,Manual destroy/create,{#key item.id}<Component/>{/key},on:change={() => component = null},Low,https://svelte.dev/docs/logic-blocks#key
47,Performance,Avoid unnecessary reactivity,Not everything needs $:,$: only for side effects,$: for simple assignments,$: if (x) console.log(x),$: y = x (when y = x works),Low,
48,Performance,Use immutable compiler option,Skip equality checks,immutable: true for large lists,Default for all components,<svelte:options immutable/>,Default without immutable,Low,
49,TypeScript,"Use lang=""ts"" in script",TypeScript support,"<script lang=""ts"">",JavaScript for typed projects,"<script lang=""ts"">",<script> with JSDoc,Medium,https://svelte.dev/docs/typescript
50,TypeScript,Type props with interface,Explicit prop types,interface $$Props for types,Untyped props,interface $$Props { name: string },export let name,Medium,
51,TypeScript,Type events with createEventDispatcher,Type-safe events,createEventDispatcher<Events>(),Untyped dispatch,createEventDispatcher<{ save: Data }>(),createEventDispatcher(),Medium,
52,Accessibility,Use semantic elements,Proper HTML in templates,button nav main appropriately,div for everything,<button on:click>,<div on:click>,High,
53,Accessibility,Add aria to dynamic content,Accessible state changes,aria-live for updates,Silent dynamic updates,"<div aria-live=""polite"">{message}</div>",<div>{message}</div>,Medium,
.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Views,Use struct for views,SwiftUI views are value types,struct MyView: View,class MyView: View,struct ContentView: View { var body: some View },class ContentView: View,High,https://developer.apple.com/documentation/swiftui/view
2,Views,Keep views small and focused,Single responsibility for each view,Extract subviews for complex layouts,Large monolithic views,Extract HeaderView FooterView,500+ line View struct,Medium,
3,Views,Use body computed property,body returns the view hierarchy,var body: some View { },func body() -> some View,"var body: some View { Text(""Hello"") }",func body() -> Text,High,
4,Views,Prefer composition over inheritance,Compose views using ViewBuilder,Combine smaller views,Inheritance hierarchies,VStack { Header() Content() },class SpecialView extends BaseView,Medium,
5,State,Use @State for local state,Simple value types owned by view,@State for view-local primitives,@State for shared data,@State private var count = 0,@State var sharedData: Model,High,https://developer.apple.com/documentation/swiftui/state
6,State,Use @Binding for two-way data,Pass mutable state to child views,@Binding for child input,@State in child for parent data,@Binding var isOn: Bool,$isOn to pass binding,Medium,https://developer.apple.com/documentation/swiftui/binding
7,State,Use @StateObject for reference types,ObservableObject owned by view,@StateObject for view-created objects,@ObservedObject for owned objects,@StateObject private var vm = ViewModel(),@ObservedObject var vm = ViewModel(),High,https://developer.apple.com/documentation/swiftui/stateobject
8,State,Use @ObservedObject for injected objects,Reference types passed from parent,@ObservedObject for injected dependencies,@StateObject for injected objects,@ObservedObject var vm: ViewModel,@StateObject var vm: ViewModel (injected),High,https://developer.apple.com/documentation/swiftui/observedobject
9,State,Use @EnvironmentObject for shared state,App-wide state injection,@EnvironmentObject for global state,Prop drilling through views,@EnvironmentObject var settings: Settings,Pass settings through 5 views,Medium,https://developer.apple.com/documentation/swiftui/environmentobject
10,State,Use @Published in ObservableObject,Automatically publish property changes,@Published for observed properties,Manual objectWillChange calls,@Published var items: [Item] = [],var items: [Item] { didSet { objectWillChange.send() } },Medium,
11,Observable,Use @Observable macro (iOS 17+),Modern observation without Combine,@Observable class for view models,ObservableObject for new projects,@Observable class ViewModel { },class ViewModel: ObservableObject,Medium,https://developer.apple.com/documentation/observation
12,Observable,Use @Bindable for @Observable,Create bindings from @Observable,@Bindable var vm for bindings,@Binding with @Observable,@Bindable var viewModel,$viewModel.name with @Observable,Medium,
13,Layout,Use VStack HStack ZStack,Standard stack-based layouts,Stacks for linear arrangements,GeometryReader for simple layouts,VStack { Text() Image() },GeometryReader for vertical list,Medium,https://developer.apple.com/documentation/swiftui/vstack
14,Layout,Use LazyVStack LazyHStack for lists,Lazy loading for performance,Lazy stacks for long lists,Regular stacks for 100+ items,LazyVStack { ForEach(items) },VStack { ForEach(largeArray) },High,https://developer.apple.com/documentation/swiftui/lazyvstack
15,Layout,Use GeometryReader sparingly,Only when needed for sizing,GeometryReader for responsive layouts,GeometryReader everywhere,GeometryReader for aspect ratio,GeometryReader wrapping everything,Medium,
16,Layout,Use spacing and padding consistently,Consistent spacing throughout app,Design system spacing values,Magic numbers for spacing,.padding(16) or .padding(),".padding(13), .padding(17)",Low,
17,Layout,Use frame modifiers correctly,Set explicit sizes when needed,.frame(maxWidth: .infinity),Fixed sizes for responsive content,.frame(maxWidth: .infinity),.frame(width: 375),Medium,
18,Modifiers,Order modifiers correctly,Modifier order affects rendering,Background before padding for full coverage,Wrong modifier order,.padding().background(Color.red),.background(Color.red).padding(),High,
19,Modifiers,Create custom ViewModifiers,Reusable modifier combinations,ViewModifier for repeated styling,Duplicate modifier chains,struct CardStyle: ViewModifier,.shadow().cornerRadius() everywhere,Medium,https://developer.apple.com/documentation/swiftui/viewmodifier
20,Modifiers,Use conditional modifiers carefully,Avoid changing view identity,if-else with same view type,Conditional that changes view identity,Text(title).foregroundColor(isActive ? .blue : .gray),if isActive { Text().bold() } else { Text() },Medium,
21,Navigation,Use NavigationStack (iOS 16+),Modern navigation with type-safe paths,NavigationStack with navigationDestination,NavigationView for new projects,NavigationStack { },NavigationView { } (deprecated),Medium,https://developer.apple.com/documentation/swiftui/navigationstack
22,Navigation,Use navigationDestination,Type-safe navigation destinations,.navigationDestination(for:),NavigationLink(destination:),.navigationDestination(for: Item.self),NavigationLink(destination: DetailView()),Medium,
23,Navigation,Use @Environment for dismiss,Programmatic navigation dismissal,@Environment(\.dismiss) var dismiss,presentationMode (deprecated),@Environment(\.dismiss) var dismiss,@Environment(\.presentationMode),Low,
24,Lists,Use List for scrollable content,Built-in scrolling and styling,List for standard scrollable content,ScrollView + VStack for simple lists,List { ForEach(items) { } },ScrollView { VStack { ForEach } },Low,https://developer.apple.com/documentation/swiftui/list
25,Lists,Provide stable identifiers,Use Identifiable or explicit id,Identifiable protocol or id parameter,Index as identifier,ForEach(items) where Item: Identifiable,"ForEach(items.indices, id: \.self)",High,
26,Lists,Use onDelete and onMove,Standard list editing,onDelete for swipe to delete,Custom delete implementation,.onDelete(perform: delete),.onTapGesture for delete,Low,
27,Forms,Use Form for settings,Grouped input controls,Form for settings screens,Manual grouping for forms,Form { Section { Toggle() } },VStack { Toggle() },Low,https://developer.apple.com/documentation/swiftui/form
28,Forms,Use @FocusState for keyboard,Manage keyboard focus,@FocusState for text field focus,Manual first responder handling,@FocusState private var isFocused: Bool,UIKit first responder,Medium,https://developer.apple.com/documentation/swiftui/focusstate
29,Forms,Validate input properly,Show validation feedback,Real-time validation feedback,Submit without validation,TextField with validation state,TextField without error handling,Medium,
30,Async,Use .task for async work,Automatic cancellation on view disappear,.task for view lifecycle async,onAppear with Task,.task { await loadData() },onAppear { Task { await loadData() } },Medium,https://developer.apple.com/documentation/swiftui/view/task(priority:_:)
31,Async,Handle loading states,Show progress during async operations,ProgressView during loading,Empty view during load,if isLoading { ProgressView() },No loading indicator,Medium,
32,Async,Use @MainActor for UI updates,Ensure UI updates on main thread,@MainActor on view models,Manual DispatchQueue.main,@MainActor class ViewModel,DispatchQueue.main.async,Medium,
33,Animation,Use withAnimation,Animate state changes,withAnimation for state transitions,No animation for state changes,withAnimation { isExpanded.toggle() },isExpanded.toggle(),Low,https://developer.apple.com/documentation/swiftui/withanimation(_:_:)
34,Animation,Use .animation modifier,Apply animations to views,.animation(.spring()) on view,Manual animation timing,.animation(.easeInOut),CABasicAnimation equivalent,Low,
35,Animation,Respect reduced motion,Check accessibility settings,Check accessibilityReduceMotion,Ignore motion preferences,@Environment(\.accessibilityReduceMotion),Always animate regardless,High,
36,Preview,Use #Preview macro (Xcode 15+),Modern preview syntax,#Preview for view previews,PreviewProvider protocol,#Preview { ContentView() },struct ContentView_Previews: PreviewProvider,Low,
37,Preview,Create multiple previews,Test different states and devices,Multiple previews for states,Single preview only,"#Preview(""Light"") { } #Preview(""Dark"") { }",Single preview configuration,Low,
38,Preview,Use preview data,Dedicated preview mock data,Static preview data,Production data in previews,Item.preview for preview,Fetch real data in preview,Low,
39,Performance,Avoid expensive body computations,Body should be fast to compute,Precompute in view model,Heavy computation in body,vm.computedValue in body,Complex calculation in body,High,
40,Performance,Use Equatable views,Skip unnecessary view updates,Equatable for complex views,Default equality for all views,struct MyView: View Equatable,No Equatable conformance,Medium,
41,Performance,Profile with Instruments,Measure before optimizing,Use SwiftUI Instruments,Guess at performance issues,Profile with Instruments,Optimize without measuring,Medium,
42,Accessibility,Add accessibility labels,Describe UI elements,.accessibilityLabel for context,Missing labels,".accessibilityLabel(""Close button"")",Button without label,High,https://developer.apple.com/documentation/swiftui/view/accessibilitylabel(_:)-1d7jv
43,Accessibility,Support Dynamic Type,Respect text size preferences,Scalable fonts and layouts,Fixed font sizes,.font(.body) with Dynamic Type,.font(.system(size: 16)),High,
44,Accessibility,Use semantic views,Proper accessibility traits,Correct accessibilityTraits,Wrong semantic meaning,Button for actions Image for display,Image that acts like button,Medium,
45,Testing,Use ViewInspector for testing,Third-party view testing,ViewInspector for unit tests,UI tests only,ViewInspector assertions,Only XCUITest,Medium,
46,Testing,Test view models,Unit test business logic,XCTest for view model,Skip view model testing,Test ViewModel methods,No unit tests,Medium,
47,Testing,Use preview as visual test,Previews catch visual regressions,Multiple preview configurations,No visual verification,Preview different states,Single preview only,Low,
48,Architecture,Use MVVM pattern,Separate view and logic,ViewModel for business logic,Logic in View,ObservableObject ViewModel,@State for complex logic,Medium,
49,Architecture,Keep views dumb,Views display view model state,View reads from ViewModel,Business logic in View,view.items from vm.items,Complex filtering in View,Medium,
50,Architecture,Use dependency injection,Inject dependencies for testing,Initialize with dependencies,Hard-coded dependencies,init(service: ServiceProtocol),let service = RealService(),Medium,
.claude/skills/ui-ux-pro-max/data/stacks/vue.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Composition,Use Composition API for new projects,Composition API offers better TypeScript support and logic reuse,<script setup> for components,Options API for new projects,<script setup>,export default { data() },Medium,https://vuejs.org/guide/extras/composition-api-faq.html
2,Composition,Use script setup syntax,Cleaner syntax with automatic exports,<script setup> with defineProps,setup() function manually,<script setup>,<script> setup() { return {} },Low,https://vuejs.org/api/sfc-script-setup.html
3,Reactivity,Use ref for primitives,ref() for primitive values that need reactivity,ref() for strings numbers booleans,reactive() for primitives,const count = ref(0),const count = reactive(0),Medium,https://vuejs.org/guide/essentials/reactivity-fundamentals.html
4,Reactivity,Use reactive for objects,reactive() for complex objects and arrays,reactive() for objects with multiple properties,ref() for complex objects,const state = reactive({ user: null }),const state = ref({ user: null }),Medium,
5,Reactivity,Access ref values with .value,Remember .value in script unwrap in template,Use .value in script,Forget .value in script,count.value++,count++ (in script),High,
6,Reactivity,Use computed for derived state,Computed properties cache and update automatically,computed() for derived values,Methods for derived values,const doubled = computed(() => count.value * 2),const doubled = () => count.value * 2,Medium,https://vuejs.org/guide/essentials/computed.html
7,Reactivity,Use shallowRef for large objects,Avoid deep reactivity for performance,shallowRef for large data structures,ref for large nested objects,const bigData = shallowRef(largeObject),const bigData = ref(largeObject),Medium,https://vuejs.org/api/reactivity-advanced.html#shallowref
8,Watchers,Use watchEffect for simple cases,Auto-tracks dependencies,watchEffect for simple reactive effects,watch with explicit deps when not needed,watchEffect(() => console.log(count.value)),"watch(count, (val) => console.log(val))",Low,https://vuejs.org/guide/essentials/watchers.html
9,Watchers,Use watch for specific sources,Explicit control over what to watch,watch with specific refs,watchEffect for complex conditional logic,"watch(userId, fetchUser)",watchEffect with conditionals,Medium,
10,Watchers,Clean up side effects,Return cleanup function in watchers,Return cleanup in watchEffect,Leave subscriptions open,watchEffect((onCleanup) => { onCleanup(unsub) }),watchEffect without cleanup,High,
11,Props,Define props with defineProps,Type-safe prop definitions,defineProps with TypeScript,Props without types,defineProps<{ msg: string }>(),defineProps(['msg']),Medium,https://vuejs.org/guide/typescript/composition-api.html#typing-component-props
12,Props,Use withDefaults for default values,Provide defaults for optional props,withDefaults with defineProps,Defaults in destructuring,"withDefaults(defineProps<Props>(), { count: 0 })",const { count = 0 } = defineProps(),Medium,
13,Props,Avoid mutating props,Props should be read-only,Emit events to parent for changes,Direct prop mutation,"emit('update:modelValue', newVal)",props.modelValue = newVal,High,
14,Emits,Define emits with defineEmits,Type-safe event emissions,defineEmits with types,Emit without definition,defineEmits<{ change: [id: number] }>(),"emit('change', id) without define",Medium,https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits
15,Emits,Use v-model for two-way binding,Simplified parent-child data flow,v-model with modelValue prop,:value + @input manually,"<Child v-model=""value""/>","<Child :value=""value"" @input=""value = $event""/>",Low,https://vuejs.org/guide/components/v-model.html
16,Lifecycle,Use onMounted for DOM access,DOM is ready in onMounted,onMounted for DOM operations,Access DOM in setup directly,onMounted(() => el.value.focus()),el.value.focus() in setup,High,https://vuejs.org/api/composition-api-lifecycle.html
17,Lifecycle,Clean up in onUnmounted,Remove listeners and subscriptions,onUnmounted for cleanup,Leave listeners attached,onUnmounted(() => window.removeEventListener()),No cleanup on unmount,High,
18,Lifecycle,Avoid onBeforeMount for data,Use onMounted or setup for data fetching,Fetch in onMounted or setup,Fetch in onBeforeMount,onMounted(async () => await fetchData()),onBeforeMount(async () => await fetchData()),Low,
19,Components,Use single-file components,Keep template script style together,.vue files for components,Separate template/script files,Component.vue with all parts,Component.js + Component.html,Low,
20,Components,Use PascalCase for components,Consistent component naming,PascalCase in imports and templates,kebab-case in script,<MyComponent/>,<my-component/>,Low,https://vuejs.org/style-guide/rules-strongly-recommended.html
21,Components,Prefer composition over mixins,Composables replace mixins,Composables for shared logic,Mixins for code reuse,const { data } = useApi(),mixins: [apiMixin],Medium,
22,Composables,Name composables with use prefix,Convention for composable functions,useFetch useAuth useForm,getData or fetchApi,export function useFetch(),export function fetchData(),Medium,https://vuejs.org/guide/reusability/composables.html
23,Composables,Return refs from composables,Maintain reactivity when destructuring,Return ref values,Return reactive objects that lose reactivity,return { data: ref(null) },return reactive({ data: null }),Medium,
24,Composables,Accept ref or value params,Use toValue for flexible inputs,toValue() or unref() for params,Only accept ref or only value,const val = toValue(maybeRef),const val = maybeRef.value,Low,https://vuejs.org/api/reactivity-utilities.html#tovalue
25,Templates,Use v-bind shorthand,Cleaner template syntax,:prop instead of v-bind:prop,Full v-bind syntax,"<div :class=""cls"">","<div v-bind:class=""cls"">",Low,
26,Templates,Use v-on shorthand,Cleaner event binding,@event instead of v-on:event,Full v-on syntax,"<button @click=""handler"">","<button v-on:click=""handler"">",Low,
27,Templates,Avoid v-if with v-for,v-if has higher priority causes issues,Wrap in template or computed filter,v-if on same element as v-for,<template v-for><div v-if>,<div v-for v-if>,High,https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for
28,Templates,Use key with v-for,Proper list rendering and updates,Unique key for each item,Index as key for dynamic lists,"v-for=""item in items"" :key=""item.id""","v-for=""(item, i) in items"" :key=""i""",High,
29,State,Use Pinia for global state,Official state management for Vue 3,Pinia stores for shared state,Vuex for new projects,const store = useCounterStore(),Vuex with mutations,Medium,https://pinia.vuejs.org/
30,State,Define stores with defineStore,Composition API style stores,Setup stores with defineStore,Options stores for complex state,"defineStore('counter', () => {})","defineStore('counter', { state })",Low,
31,State,Use storeToRefs for destructuring,Maintain reactivity when destructuring,storeToRefs(store),Direct destructuring,const { count } = storeToRefs(store),const { count } = store,High,https://pinia.vuejs.org/core-concepts/#destructuring-from-a-store
32,Routing,Use useRouter and useRoute,Composition API router access,useRouter() useRoute() in setup,this.$router this.$route,const router = useRouter(),this.$router.push(),Medium,https://router.vuejs.org/guide/advanced/composition-api.html
33,Routing,Lazy load route components,Code splitting for routes,() => import() for components,Static imports for all routes,component: () => import('./Page.vue'),component: Page,Medium,https://router.vuejs.org/guide/advanced/lazy-loading.html
34,Routing,Use navigation guards,Protect routes and handle redirects,beforeEach for auth checks,Check auth in each component,router.beforeEach((to) => {}),Check auth in onMounted,Medium,
35,Performance,Use v-once for static content,Skip re-renders for static elements,v-once on never-changing content,v-once on dynamic content,<div v-once>{{ staticText }}</div>,<div v-once>{{ dynamicText }}</div>,Low,https://vuejs.org/api/built-in-directives.html#v-once
36,Performance,Use v-memo for expensive lists,Memoize list items,v-memo with dependency array,Re-render entire list always,"<div v-for v-memo=""[item.id]"">",<div v-for> without memo,Medium,https://vuejs.org/api/built-in-directives.html#v-memo
37,Performance,Use shallowReactive for flat objects,Avoid deep reactivity overhead,shallowReactive for flat state,reactive for simple objects,shallowReactive({ count: 0 }),reactive({ count: 0 }),Low,
38,Performance,Use defineAsyncComponent,Lazy load heavy components,defineAsyncComponent for modals dialogs,Import all components eagerly,defineAsyncComponent(() => import()),import HeavyComponent from,Medium,https://vuejs.org/guide/components/async.html
39,TypeScript,Use generic components,Type-safe reusable components,Generic with defineComponent,Any types in components,"<script setup lang=""ts"" generic=""T"">",<script setup> without types,Medium,https://vuejs.org/guide/typescript/composition-api.html
40,TypeScript,Type template refs,Proper typing for DOM refs,ref<HTMLInputElement>(null),ref(null) without type,const input = ref<HTMLInputElement>(null),const input = ref(null),Medium,
41,TypeScript,Use PropType for complex props,Type complex prop types,PropType<User> for object props,Object without type,type: Object as PropType<User>,type: Object,Medium,
42,Testing,Use Vue Test Utils,Official testing library,mount shallowMount for components,Manual DOM testing,import { mount } from '@vue/test-utils',document.createElement,Medium,https://test-utils.vuejs.org/
43,Testing,Test component behavior,Focus on inputs and outputs,Test props emit and rendered output,Test internal implementation,expect(wrapper.text()).toContain(),expect(wrapper.vm.internalState),Medium,
44,Forms,Use v-model modifiers,Built-in input handling,.lazy .number .trim modifiers,Manual input parsing,"<input v-model.number=""age"">","<input v-model=""age""> then parse",Low,https://vuejs.org/guide/essentials/forms.html#modifiers
45,Forms,Use VeeValidate or FormKit,Form validation libraries,VeeValidate for complex forms,Manual validation logic,useField useForm from vee-validate,Custom validation in each input,Medium,
46,Accessibility,Use semantic elements,Proper HTML elements in templates,button nav main for purpose,div for everything,<button @click>,<div @click>,High,
47,Accessibility,Bind aria attributes dynamically,Keep ARIA in sync with state,":aria-expanded=""isOpen""",Static ARIA values,":aria-expanded=""menuOpen""","aria-expanded=""true""",Medium,
48,SSR,Use Nuxt for SSR,Full-featured SSR framework,Nuxt 3 for SSR apps,Manual SSR setup,npx nuxi init my-app,Custom SSR configuration,Medium,https://nuxt.com/
49,SSR,Handle hydration mismatches,Client/server content must match,ClientOnly for browser-only content,Different content server/client,<ClientOnly><BrowserWidget/></ClientOnly>,<div>{{ Date.now() }}</div>,High,
.claude/skills/ui-ux-pro-max/data/styles.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
No,Style Category,Type,Keywords,Primary Colors,Secondary Colors,Effects & Animation,Best For,Do Not Use For,Light Mode âœ“,Dark Mode âœ“,Performance,Accessibility,Mobile-Friendly,Conversion-Focused,Framework Compatibility,Era/Origin,Complexity,AI Prompt Keywords,CSS/Technical Keywords,Implementation Checklist,Design System Variables
1,Minimalism & Swiss Style,General,"Clean, simple, spacious, functional, white space, high contrast, geometric, sans-serif, grid-based, essential","Monochromatic, Black #000000, White #FFFFFF","Neutral (Beige #F5F1E8, Grey #808080, Taupe #B38B6D), Primary accent","Subtle hover (200-250ms), smooth transitions, sharp shadows if any, clear type hierarchy, fast loading","Enterprise apps, dashboards, documentation sites, SaaS platforms, professional tools","Creative portfolios, entertainment, playful brands, artistic experiments",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,◐ Medium,"Tailwind 10/10, Bootstrap 9/10, MUI 9/10",1950s Swiss,Low,"Design a minimalist landing page. Use: white space, geometric layouts, sans-serif fonts, high contrast, grid-based structure, essential elements only. Avoid shadows and gradients. Focus on clarity and functionality.","display: grid, gap: 2rem, font-family: sans-serif, color: #000 or #FFF, max-width: 1200px, clean borders, no box-shadow unless necessary","☐ Grid-based layout 12-16 columns, â˜ Typography hierarchy clear, â˜ No unnecessary decorations, â˜ WCAG AAA contrast verified, â˜ Mobile responsive grid","--spacing: 2rem, --border-radius: 0px, --font-weight: 400-700, --shadow: none, --accent-color: single primary only"
2,Neumorphism,General,"Soft UI, embossed, debossed, convex, concave, light source, subtle depth, rounded (12-16px), monochromatic","Light pastels: Soft Blue #C8E0F4, Soft Pink #F5E0E8, Soft Grey #E8E8E8","Tints/shades (±30%), gradient subtlety, color harmony","Soft box-shadow (multiple: -5px -5px 15px, 5px 5px 15px), smooth press (150ms), inner subtle shadow","Health/wellness apps, meditation platforms, fitness trackers, minimal interaction UIs","Complex apps, critical accessibility, data-heavy dashboards, high-contrast required",✓ Full,◐ Partial,⚡ Good,⚠ Low contrast,✓ Good,◐ Medium,"Tailwind 8/10, CSS-in-JS 9/10",2020s Modern,Medium,"Create a neumorphic UI with soft 3D effects. Use light pastels, rounded corners (12-16px), subtle soft shadows (multiple layers), no hard lines, monochromatic color scheme with light/dark variations. Embossed/debossed effect on interactive elements.","border-radius: 12-16px, box-shadow: -5px -5px 15px rgba(0,0,0,0.1), 5px 5px 15px rgba(255,255,255,0.8), background: linear-gradient(145deg, color1, color2), transform: scale on press","☐ Rounded corners 12-16px consistent, â˜ Multiple shadow layers (2-3), â˜ Pastel color verified, â˜ Monochromatic palette checked, â˜ Press animation smooth 150ms","--border-radius: 14px, --shadow-soft-1: -5px -5px 15px, --shadow-soft-2: 5px 5px 15px, --color-light: #F5F5F5, --color-primary: single pastel"
3,Glassmorphism,General,"Frosted glass, transparent, blurred background, layered, vibrant background, light source, depth, multi-layer","Translucent white: rgba(255,255,255,0.1-0.3)","Vibrant: Electric Blue #0080FF, Neon Purple #8B00FF, Vivid Pink #FF1493, Teal #20B2AA","Backdrop blur (10-20px), subtle border (1px solid rgba white 0.2), light reflection, Z-depth","Modern SaaS, financial dashboards, high-end corporate, lifestyle apps, modal overlays, navigation","Low-contrast backgrounds, critical accessibility, performance-limited, dark text on dark",✓ Full,✓ Full,⚠ Good,⚠ Ensure 4.5:1,✓ Good,✓ High,"Tailwind 9/10, MUI 8/10, Chakra 8/10",2020s Modern,Medium,"Design a glassmorphic interface with frosted glass effect. Use backdrop blur (10-20px), translucent overlays (rgba 10-30% opacity), vibrant background colors, subtle borders, light source reflection, layered depth. Perfect for modern overlays and cards.","backdrop-filter: blur(15px), background: rgba(255, 255, 255, 0.15), border: 1px solid rgba(255,255,255,0.2), -webkit-backdrop-filter: blur(15px), z-index layering for depth","☐ Backdrop-filter blur 10-20px, â˜ Translucent white 15-30% opacity, â˜ Subtle border 1px light, â˜ Vibrant background verified, â˜ Text contrast 4.5:1 checked","--blur-amount: 15px, --glass-opacity: 0.15, --border-color: rgba(255,255,255,0.2), --background: vibrant color, --text-color: light/dark based on BG"
4,Brutalism,General,"Raw, unpolished, stark, high contrast, plain text, default fonts, visible borders, asymmetric, anti-design","Primary: Red #FF0000, Blue #0000FF, Yellow #FFFF00, Black #000000, White #FFFFFF","Limited: Neon Green #00FF00, Hot Pink #FF00FF, minimal secondary","No smooth transitions (instant), sharp corners (0px), bold typography (700+), visible grid, large blocks","Design portfolios, artistic projects, counter-culture brands, editorial/media sites, tech blogs","Corporate environments, conservative industries, critical accessibility, customer-facing professional",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,◐ Medium,✗ Low,"Tailwind 10/10, Bootstrap 7/10",1950s Brutalist,Low,"Create a brutalist design with raw, unpolished, stark aesthetic. Use pure primary colors (red, blue, yellow), black & white, no smooth transitions (instant), sharp corners, bold large typography, visible grid lines, default system fonts, intentional 'broken' design elements.","border-radius: 0px, transition: none or 0s, font-family: system-ui or monospace, font-weight: 700+, border: visible 2-4px, colors: #FF0000, #0000FF, #FFFF00, #000000, #FFFFFF","☐ No border-radius (0px), â˜ No transitions (instant), â˜ Bold typography (700+), â˜ Pure primary colors used, â˜ Visible grid/borders, â˜ Asymmetric layout intentional","--border-radius: 0px, --transition-duration: 0s, --font-weight: 700-900, --colors: primary only, --border-style: visible, --grid-visible: true"
5,3D & Hyperrealism,General,"Depth, realistic textures, 3D models, spatial navigation, tactile, skeuomorphic elements, rich detail, immersive","Deep Navy #001F3F, Forest Green #228B22, Burgundy #800020, Gold #FFD700, Silver #C0C0C0","Complex gradients (5-10 stops), realistic lighting, shadow variations (20-40% darker)","WebGL/Three.js 3D, realistic shadows (layers), physics lighting, parallax (3-5 layers), smooth 3D (300-400ms)","Gaming, product showcase, immersive experiences, high-end e-commerce, architectural viz, VR/AR","Low-end mobile, performance-limited, critical accessibility, data tables/forms",◐ Partial,◐ Partial,❌ Poor,⚠ Not accessible,✗ Low,◐ Medium,"Three.js 10/10, R3F 10/10, Babylon.js 10/10",2020s Modern,High,"Build an immersive 3D interface using realistic textures, 3D models (Three.js/Babylon.js), complex shadows, realistic lighting, parallax scrolling (3-5 layers), physics-based motion. Include skeuomorphic elements with tactile detail.","transform: translate3d, perspective: 1000px, WebGL canvas, Three.js/Babylon.js library, box-shadow: complex multi-layer, background: complex gradients, filter: drop-shadow()","☐ WebGL/Three.js integrated, â˜ 3D models loaded, â˜ Parallax 3-5 layers, â˜ Realistic lighting verified, â˜ Complex shadows rendered, â˜ Physics animation smooth 300-400ms","--perspective: 1000px, --parallax-layers: 5, --lighting-intensity: realistic, --shadow-depth: 20-40%, --animation-duration: 300-400ms"
6,Vibrant & Block-based,General,"Bold, energetic, playful, block layout, geometric shapes, high color contrast, duotone, modern, energetic","Neon Green #39FF14, Electric Purple #BF00FF, Vivid Pink #FF1493, Bright Cyan #00FFFF, Sunburst #FFAA00","Complementary: Orange #FF7F00, Shocking Pink #FF006E, Lime #CCFF00, triadic schemes","Large sections (48px+ gaps), animated patterns, bold hover (color shift), scroll-snap, large type (32px+), 200-300ms","Startups, creative agencies, gaming, social media, youth-focused, entertainment, consumer","Financial institutions, healthcare, formal business, government, conservative, elderly",✓ Full,✓ Full,⚡ Good,◐ Ensure WCAG,✓ High,✓ High,"Tailwind 10/10, Chakra 9/10, Styled 9/10",2020s Modern,Medium,"Design an energetic, vibrant interface with bold block layouts, geometric shapes, high color contrast, large typography (32px+), animated background patterns, duotone effects. Perfect for startups and youth-focused apps. Use 4-6 contrasting colors from complementary/triadic schemes.","display: flex/grid with large gaps (48px+), font-size: 32px+, background: animated patterns (CSS), color: neon/vibrant colors, animation: continuous pattern movement","☐ Block layout with 48px+ gaps, â˜ Large typography 32px+, â˜ 4-6 vibrant colors max, â˜ Animated patterns active, â˜ Scroll-snap enabled, â˜ High contrast verified (7:1+)","--block-gap: 48px, --typography-size: 32px+, --color-palette: 4-6 vibrant colors, --animation: continuous pattern, --contrast-ratio: 7:1+"
7,Dark Mode (OLED),General,"Dark theme, low light, high contrast, deep black, midnight blue, eye-friendly, OLED, night mode, power efficient","Deep Black #000000, Dark Grey #121212, Midnight Blue #0A0E27","Vibrant accents: Neon Green #39FF14, Electric Blue #0080FF, Gold #FFD700, Plasma Purple #BF00FF","Minimal glow (text-shadow: 0 0 10px), dark-to-light transitions, low white emission, high readability, visible focus","Night-mode apps, coding platforms, entertainment, eye-strain prevention, OLED devices, low-light","Print-first content, high-brightness outdoor, color-accuracy-critical",✗ No,✓ Only,⚡ Excellent,✓ WCAG AAA,✓ High,◐ Low,"Tailwind 10/10, MUI 10/10, Chakra 10/10",2020s Modern,Low,"Create an OLED-optimized dark interface with deep black (#000000), dark grey (#121212), midnight blue accents. Use minimal glow effects, vibrant neon accents (green, blue, gold, purple), high contrast text. Optimize for eye comfort and OLED power saving.","background: #000000 or #121212, color: #FFFFFF or #E0E0E0, text-shadow: 0 0 10px neon-color (sparingly), filter: brightness(0.8) if needed, color-scheme: dark","☐ Deep black #000000 or #121212, â˜ Vibrant neon accents used, â˜ Text contrast 7:1+, â˜ Minimal glow effects, â˜ OLED power optimization, â˜ No white (#FFFFFF) background","--bg-black: #000000, --bg-dark-grey: #121212, --text-primary: #FFFFFF, --accent-neon: neon colors, --glow-effect: minimal, --oled-optimized: true"
8,Accessible & Ethical,General,"High contrast, large text (16px+), keyboard navigation, screen reader friendly, WCAG compliant, focus state, semantic","WCAG AA/AAA (4.5:1 min), simple primary, clear secondary, high luminosity (7:1+)","Symbol-based colors (not color-only), supporting patterns, inclusive combinations","Clear focus rings (3-4px), ARIA labels, skip links, responsive design, reduced motion, 44x44px touch targets","Government, healthcare, education, inclusive products, large audience, legal compliance, public",None - accessibility universal,✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ High,All frameworks 10/10,Universal,Low,"Design with WCAG AAA compliance. Include: high contrast (7:1+), large text (16px+), keyboard navigation, screen reader compatibility, focus states visible (3-4px ring), semantic HTML, ARIA labels, skip links, reduced motion support (prefers-reduced-motion), 44x44px touch targets.","color-contrast: 7:1+, font-size: 16px+, outline: 3-4px on :focus-visible, aria-label, role attributes, @media (prefers-reduced-motion), touch-target: 44x44px, cursor: pointer","☐ WCAG AAA verified, â˜ 7:1+ contrast checked, â˜ Keyboard navigation tested, â˜ Screen reader tested, â˜ Focus visible 3-4px, â˜ Semantic HTML used, â˜ Touch targets 44x44px","--contrast-ratio: 7:1, --font-size-min: 16px, --focus-ring: 3-4px, --touch-target: 44x44px, --wcag-level: AAA, --keyboard-accessible: true, --sr-tested: true"
9,Claymorphism,General,"Soft 3D, chunky, playful, toy-like, bubbly, thick borders (3-4px), double shadows, rounded (16-24px)","Pastel: Soft Peach #FDBCB4, Baby Blue #ADD8E6, Mint #98FF98, Lilac #E6E6FA, light BG","Soft gradients (pastel-to-pastel), light/dark variations (20-30%), gradient subtle","Inner+outer shadows (subtle, no hard lines), soft press (200ms ease-out), fluffy elements, smooth transitions","Educational apps, children's apps, SaaS platforms, creative tools, fun-focused, onboarding, casual games","Formal corporate, professional services, data-critical, serious/medical, legal apps, finance",✓ Full,◐ Partial,⚡ Good,⚠ Ensure 4.5:1,✓ High,✓ High,"Tailwind 9/10, CSS-in-JS 9/10",2020s Modern,Medium,"Design a playful, toy-like interface with soft 3D, chunky elements, bubbly aesthetic, rounded edges (16-24px), thick borders (3-4px), double shadows (inner + outer), pastel colors, smooth animations. Perfect for children's apps and creative tools.","border-radius: 16-24px, border: 3-4px solid, box-shadow: inset -2px -2px 8px, 4px 4px 8px, background: pastel-gradient, animation: soft bounce (cubic-bezier 0.34, 1.56)","☐ Border-radius 16-24px, â˜ Thick borders 3-4px, â˜ Double shadows (inner+outer), â˜ Pastel colors used, â˜ Soft bounce animations, â˜ Playful interactions","--border-radius: 20px, --border-width: 3-4px, --shadow-inner: inset -2px -2px 8px, --shadow-outer: 4px 4px 8px, --color-palette: pastels, --animation: bounce"
10,Aurora UI,General,"Vibrant gradients, smooth blend, Northern Lights effect, mesh gradient, luminous, atmospheric, abstract","Complementary: Blue-Orange, Purple-Yellow, Electric Blue #0080FF, Magenta #FF1493, Cyan #00FFFF","Smooth transitions (Blue→Purple→Pink→Teal), iridescent effects, blend modes (screen, multiply)","Large flowing CSS/SVG gradients, subtle 8-12s animations, depth via color layering, smooth morph","Modern SaaS, creative agencies, branding, music platforms, lifestyle, premium products, hero sections","Data-heavy dashboards, critical accessibility, content-heavy where distraction issues",✓ Full,✓ Full,⚠ Good,⚠ Text contrast,✓ Good,✓ High,"Tailwind 9/10, CSS-in-JS 10/10",2020s Modern,Medium,"Create a vibrant gradient interface inspired by Northern Lights with mesh gradients, smooth color blends, flowing animations. Use complementary color pairs (blue-orange, purple-yellow), flowing background gradients, subtle continuous animations (8-12s loops), iridescent effects.","background: conic-gradient or radial-gradient with multiple stops, animation: @keyframes gradient (8-12s), background-size: 200% 200%, filter: saturate(1.2), blend-mode: screen or multiply","☐ Mesh/flowing gradients applied, â˜ 8-12s animation loop, â˜ Complementary colors used, â˜ Smooth color transitions, â˜ Iridescent effect subtle, â˜ Text contrast verified","--gradient-colors: complementary pairs, --animation-duration: 8-12s, --blend-mode: screen, --color-saturation: 1.2, --effect: iridescent, --loop-smooth: true"
11,Retro-Futurism,General,"Vintage sci-fi, 80s aesthetic, neon glow, geometric patterns, CRT scanlines, pixel art, cyberpunk, synthwave","Neon Blue #0080FF, Hot Pink #FF006E, Cyan #00FFFF, Deep Black #1A1A2E, Purple #5D34D0","Metallic Silver #C0C0C0, Gold #FFD700, duotone, 80s Pink #FF10F0, neon accents","CRT scanlines (::before overlay), neon glow (text-shadow+box-shadow), glitch effects (skew/offset keyframes)","Gaming, entertainment, music platforms, tech brands, artistic projects, nostalgic, cyberpunk","Conservative industries, critical accessibility, professional/corporate, elderly, legal/finance",✓ Full,✓ Dark focused,⚠ Moderate,⚠ High contrast/strain,◐ Medium,◐ Medium,"Tailwind 8/10, CSS-in-JS 9/10",1980s Retro,Medium,"Build a retro-futuristic (cyberpunk/vaporwave) interface with neon colors (blue, pink, cyan), deep black background, 80s aesthetic, CRT scanlines, glitch effects, neon glow text/borders, monospace fonts, geometric patterns. Use neon text-shadow and animated glitch effects.","color: neon colors (#0080FF, #FF006E, #00FFFF), text-shadow: 0 0 10px neon, background: #000 or #1A1A2E, font-family: monospace, animation: glitch (skew+offset), filter: hue-rotate","☐ Neon colors used, â˜ CRT scanlines effect, â˜ Glitch animations active, â˜ Monospace font, â˜ Deep black background, â˜ Glow effects applied, â˜ 80s patterns present","--neon-colors: #0080FF #FF006E #00FFFF, --background: #000000, --font-family: monospace, --effect: glitch+glow, --scanline-opacity: 0.3, --crt-effect: true"
12,Flat Design,General,"2D, minimalist, bold colors, no shadows, clean lines, simple shapes, typography-focused, modern, icon-heavy","Solid bright: Red, Orange, Blue, Green, limited palette (4-6 max)","Complementary colors, muted secondaries, high saturation, clean accents","No gradients/shadows, simple hover (color/opacity shift), fast loading, clean transitions (150-200ms ease), minimal icons","Web apps, mobile apps, cross-platform, startup MVPs, user-friendly, SaaS, dashboards, corporate","Complex 3D, premium/luxury, artistic portfolios, immersive experiences, high-detail",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ High,"Tailwind 10/10, Bootstrap 10/10, MUI 9/10",2010s Modern,Low,"Create a flat, 2D interface with bold colors, no shadows/gradients, clean lines, simple geometric shapes, icon-heavy, typography-focused, minimal ornamentation. Use 4-6 solid, bright colors in a limited palette with high saturation.","box-shadow: none, background: solid color, border-radius: 0-4px, color: solid (no gradients), fill: solid, stroke: 1-2px, font: bold sans-serif, icons: simplified SVG","☐ No shadows/gradients, â˜ 4-6 solid colors max, â˜ Clean lines consistent, â˜ Simple shapes used, â˜ Icon-heavy layout, â˜ High saturation colors, â˜ Fast loading verified","--shadow: none, --color-palette: 4-6 solid, --border-radius: 2px, --gradient: none, --icons: simplified SVG, --animation: minimal 150-200ms"
13,Skeuomorphism,General,"Realistic, texture, depth, 3D appearance, real-world metaphors, shadows, gradients, tactile, detailed, material","Rich realistic: wood, leather, metal colors, detailed gradients (8-12 stops), metallic effects","Realistic lighting gradients, shadow variations (30-50% darker), texture overlays, material colors","Realistic shadows (layers), depth (perspective), texture details (noise, grain), realistic animations (300-500ms)","Legacy apps, gaming, immersive storytelling, premium products, luxury, realistic simulations, education","Modern enterprise, critical accessibility, low-performance, web (use Flat/Modern)",◐ Partial,◐ Partial,❌ Poor,⚠ Textures reduce readability,✗ Low,◐ Medium,"CSS-in-JS 7/10, Custom 8/10",2007-2012 iOS,High,"Design a realistic, textured interface with 3D depth, real-world metaphors (leather, wood, metal), complex gradients (8-12 stops), realistic shadows, grain/texture overlays, tactile press animations. Perfect for premium/luxury products.","background: complex gradient (8-12 stops), box-shadow: realistic multi-layer, background-image: texture overlay (noise, grain), filter: drop-shadow, transform: scale on press (300-500ms)","☐ Realistic textures applied, â˜ Complex gradients 8-12 stops, â˜ Multi-layer shadows, â˜ Texture overlays present, â˜ Tactile animations smooth, â˜ Depth effect pronounced","--gradient-stops: 8-12, --texture-overlay: noise+grain, --shadow-layers: 3+, --animation-duration: 300-500ms, --depth-effect: pronounced, --tactile: true"
14,Liquid Glass,General,"Flowing glass, morphing, smooth transitions, fluid effects, translucent, animated blur, iridescent, chromatic aberration","Vibrant iridescent (rainbow spectrum), translucent base with opacity shifts, gradient fluidity","Chromatic aberration (Red-Cyan), iridescent oil-spill, fluid gradient blends, holographic effects","Morphing elements (SVG/CSS), fluid animations (400-600ms curves), dynamic blur (backdrop-filter), color transitions","Premium SaaS, high-end e-commerce, creative platforms, branding experiences, luxury portfolios","Performance-limited, critical accessibility, complex data, budget projects",✓ Full,✓ Full,⚠ Moderate-Poor,⚠ Text contrast,◐ Medium,✓ High,"Framer Motion 10/10, GSAP 10/10",2020s Modern,High,"Create a premium liquid glass effect with morphing shapes, flowing animations, chromatic aberration, iridescent gradients, smooth 400-600ms transitions. Use SVG morphing for shape changes, dynamic blur, smooth color transitions creating a fluid, premium feel.","animation: morphing SVG paths (400-600ms), backdrop-filter: blur + saturate, filter: hue-rotate + brightness, blend-mode: screen, background: iridescent gradient","☐ Morphing animations 400-600ms, â˜ Chromatic aberration applied, â˜ Dynamic blur active, â˜ Iridescent gradients, â˜ Smooth color transitions, â˜ Premium feel achieved","--morph-duration: 400-600ms, --blur-amount: 15px, --chromatic-aberration: true, --iridescent: true, --blend-mode: screen, --smooth-transitions: true"
15,Motion-Driven,General,"Animation-heavy, microinteractions, smooth transitions, scroll effects, parallax, entrance anim, page transitions","Bold colors emphasize movement, high contrast animated, dynamic gradients, accent action colors","Transitional states, success (Green #22C55E), error (Red #EF4444), neutral feedback","Scroll anim (Intersection Observer), hover (300-400ms), entrance, parallax (3-5 layers), page transitions","Portfolio sites, storytelling platforms, interactive experiences, entertainment apps, creative, SaaS","Data dashboards, critical accessibility, low-power devices, content-heavy, motion-sensitive",✓ Full,✓ Full,⚠ Good,⚠ Prefers-reduced-motion,✓ Good,✓ High,"GSAP 10/10, Framer Motion 10/10",2020s Modern,High,"Build an animation-heavy interface with scroll-triggered animations, microinteractions, parallax scrolling (3-5 layers), smooth transitions (300-400ms), entrance animations, page transitions. Use Intersection Observer for scroll effects, transform for performance, GPU acceleration.","animation: @keyframes scroll-reveal, transform: translateY/X, Intersection Observer API, will-change: transform, scroll-behavior: smooth, animation-duration: 300-400ms","☐ Scroll animations active, â˜ Parallax 3-5 layers, â˜ Entrance animations smooth, â˜ Page transitions fluid, â˜ GPU accelerated, â˜ Prefers-reduced-motion respected","--animation-duration: 300-400ms, --parallax-layers: 5, --scroll-behavior: smooth, --gpu-accelerated: true, --entrance-animation: true, --page-transition: smooth"
16,Micro-interactions,General,"Small animations, gesture-based, tactile feedback, subtle animations, contextual interactions, responsive","Subtle color shifts (10-20%), feedback: Green #22C55E, Red #EF4444, Amber #F59E0B","Accent feedback, neutral supporting, clear action indicators","Small hover (50-100ms), loading spinners, success/error state anim, gesture-triggered (swipe/pinch), haptic","Mobile apps, touchscreen UIs, productivity tools, user-friendly, consumer apps, interactive components","Desktop-only, critical performance, accessibility-first (alternatives needed)",✓ Full,✓ Full,⚡ Excellent,✓ Good,✓ High,✓ High,"Framer Motion 10/10, React Spring 9/10",2020s Modern,Medium,"Design with delightful micro-interactions: small 50-100ms animations, gesture-based responses, tactile feedback, loading spinners, success/error states, subtle hover effects, haptic feedback triggers for mobile. Focus on responsive, contextual interactions.","animation: short 50-100ms, transition: hover states, @media (hover: hover) for desktop, :active for press, haptic-feedback CSS/API, loading animation smooth loop","☐ Micro-animations 50-100ms, â˜ Gesture-responsive, â˜ Tactile feedback visual/haptic, â˜ Loading spinners smooth, â˜ Success/error states clear, â˜ Hover effects subtle","--micro-animation-duration: 50-100ms, --gesture-responsive: true, --haptic-feedback: true, --loading-animation: smooth, --state-feedback: success+error"
17,Inclusive Design,General,"Accessible, color-blind friendly, high contrast, haptic feedback, voice interaction, screen reader, WCAG AAA, universal","WCAG AAA (7:1+ contrast), avoid red-green only, symbol-based indicators, high contrast primary","Supporting patterns (stripes, dots, hatch), symbols, combinations, clear non-color indicators","Haptic feedback (vibration), voice guidance, focus indicators (4px+ ring), motion options, alt content, semantic","Public services, education, healthcare, finance, government, accessible consumer, inclusive",None - accessibility universal,✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ High,All frameworks 10/10,Universal,Low,"Design for universal accessibility: high contrast (7:1+), large text (16px+), keyboard-only navigation, screen reader optimization, WCAG AAA compliance, symbol-based color indicators (not color-only), haptic feedback, voice interaction support, reduced motion options.","aria-* attributes complete, role attributes semantic, focus-visible: 3-4px ring, color-contrast: 7:1+, @media (prefers-reduced-motion), alt text on all images, form labels properly associated","☐ WCAG AAA verified, â˜ 7:1+ contrast all text, â˜ Keyboard accessible (Tab/Enter), â˜ Screen reader tested, â˜ Focus visible 3-4px, â˜ No color-only indicators, â˜ Haptic fallback","--contrast-ratio: 7:1, --font-size: 16px+, --keyboard-accessible: true, --sr-compatible: true, --wcag-level: AAA, --color-symbols: true, --haptic: enabled"
18,Zero Interface,General,"Minimal visible UI, voice-first, gesture-based, AI-driven, invisible controls, predictive, context-aware, ambient","Neutral backgrounds: Soft white #FAFAFA, light grey #F0F0F0, warm off-white #F5F1E8","Subtle feedback: light green, light red, minimal UI elements, soft accents","Voice recognition UI, gesture detection, AI predictions (smooth reveal), progressive disclosure, smart suggestions","Voice assistants, AI platforms, future-forward UX, smart home, contextual computing, ambient experiences","Complex workflows, data-entry heavy, traditional systems, legacy support, explicit control",✓ Full,✓ Full,⚡ Excellent,✓ Excellent,✓ High,✓ High,"Tailwind 10/10, Custom 10/10",2020s AI-Era,Low,"Create a voice-first, gesture-based, AI-driven interface with minimal visible UI, progressive disclosure, voice recognition UI, gesture detection, AI predictions, smart suggestions, context-aware actions. Hide controls until needed.","voice-commands: Web Speech API, gesture-detection: touch events, AI-predictions: hidden by default (reveal on hover), progressive-disclosure: show on demand, minimal UI visible","☐ Voice commands responsive, â˜ Gesture detection active, â˜ AI predictions hidden/revealed, â˜ Progressive disclosure working, â˜ Minimal visible UI, â˜ Smart suggestions contextual","--voice-ui: enabled, --gesture-detection: active, --ai-predictions: smart, --progressive-disclosure: true, --visible-ui: minimal, --context-aware: true"
19,Soft UI Evolution,General,"Evolved soft UI, better contrast, modern aesthetics, subtle depth, accessibility-focused, improved shadows, hybrid","Improved contrast pastels: Soft Blue #87CEEB, Soft Pink #FFB6C1, Soft Green #90EE90, better hierarchy","Better combinations, accessible secondary, supporting with improved contrast, modern accents","Improved shadows (softer than flat, clearer than neumorphism), modern (200-300ms), focus visible, WCAG AA/AAA","Modern enterprise apps, SaaS platforms, health/wellness, modern business tools, professional, hybrid","Extreme minimalism, critical performance, systems without modern OS",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA+,✓ High,✓ High,"Tailwind 9/10, MUI 9/10, Chakra 9/10",2020s Modern,Medium,"Design evolved neumorphism with improved contrast (WCAG AA+), modern aesthetics, subtle depth, accessibility focus. Use soft shadows (softer than flat but clearer than pure neumorphism), better color hierarchy, improved focus states, modern 200-300ms animations.","box-shadow: softer multi-layer (0 2px 4px), background: improved contrast pastels, border-radius: 8-12px, animation: 200-300ms smooth, outline: 2-3px on focus, contrast: 4.5:1+","☐ Improved contrast AA/AAA, â˜ Soft shadows modern, â˜ Border-radius 8-12px, â˜ Animations 200-300ms, â˜ Focus states visible, â˜ Color hierarchy clear","--shadow-soft: modern blend, --border-radius: 10px, --animation-duration: 200-300ms, --contrast-ratio: 4.5:1+, --color-hierarchy: improved, --wcag-level: AA+"
20,Hero-Centric Design,Landing Page,"Large hero section, compelling headline, high-contrast CTA, product showcase, value proposition, hero image/video, dramatic visual","Brand primary color, white/light backgrounds for contrast, accent color for CTA","Supporting colors for secondary CTAs, accent highlights, trust elements (testimonials, logos)","Smooth scroll reveal, fade-in animations on hero, subtle background parallax, CTA glow/pulse effect","SaaS landing pages, product launches, service landing pages, B2B platforms, tech companies","Complex navigation, multi-page experiences, data-heavy applications",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,✓ Full,✓ Very High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a hero-centric landing page. Use: full-width hero section, compelling headline (60-80 chars), high-contrast CTA button, product screenshot or video, value proposition above fold, gradient or image background, clear visual hierarchy.","min-height: 100vh, display: flex, align-items: center, background: linear-gradient or image, text-shadow for readability, max-width: 800px for text, button with hover scale (1.05)","☐ Hero section full viewport height, â˜ Headline visible above fold, â˜ CTA button high contrast, â˜ Background image optimized (WebP), â˜ Text readable on background, â˜ Mobile responsive layout","--hero-min-height: 100vh, --headline-size: clamp(2rem, 5vw, 4rem), --cta-padding: 1rem 2rem, --overlay-opacity: 0.5, --text-shadow: 0 2px 4px rgba(0,0,0,0.3)"
21,Conversion-Optimized,Landing Page,"Form-focused, minimalist design, single CTA focus, high contrast, urgency elements, trust signals, social proof, clear value","Primary brand color, high-contrast white/light backgrounds, warning/urgency colors for time-limited offers","Secondary CTA color (muted), trust element colors (testimonial highlights), accent for key benefits","Hover states on CTA (color shift, slight scale), form field focus animations, loading spinner, success feedback","E-commerce product pages, free trial signups, lead generation, SaaS pricing pages, limited-time offers","Complex feature explanations, multi-product showcases, technical documentation",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ Full (mobile-optimized),✓ Very High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a conversion-optimized landing page. Use: single primary CTA, minimal distractions, trust badges, urgency elements (limited time), social proof (testimonials), clear value proposition, form above fold, progress indicators.","form with focus states, input:focus ring, button: primary color high contrast, position: sticky for CTA, max-width: 600px for form, loading spinner, success/error states","☐ Single primary CTA visible, â˜ Form fields minimal (3-5), â˜ Trust badges present, â˜ Social proof above fold, â˜ Mobile form optimized, â˜ Loading states implemented, â˜ A/B test ready","--cta-color: high contrast primary, --form-max-width: 600px, --input-height: 48px, --focus-ring: 3px solid accent, --success-color: #22C55E, --error-color: #EF4444"
22,Feature-Rich Showcase,Landing Page,"Multiple feature sections, grid layout, benefit cards, visual feature demonstrations, interactive elements, problem-solution pairs","Primary brand, bright secondary colors for feature cards, contrasting accent for CTAs","Supporting colors for: benefits (green), problems (red/orange), features (blue/purple), social proof (neutral)","Card hover effects (lift/scale), icon animations on scroll, feature toggle animations, smooth section transitions","Enterprise SaaS, software tools landing pages, platform services, complex product explanations, B2B products","Simple product pages, early-stage startups with few features, entertainment landing pages",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,✓ Good,✓ High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a feature showcase landing page. Use: grid layout for features (3-4 columns), feature cards with icons, benefit-focused copy, alternating sections, comparison tables, interactive demos, problem-solution pairs.","display: grid, grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)), gap: 2rem, card hover effects (translateY -4px), icon containers, alternating background colors","☐ Feature grid responsive, â˜ Icons consistent style, â˜ Card hover effects smooth, â˜ Alternating sections contrast, â˜ Benefits clearly stated, â˜ Mobile stacks properly","--card-padding: 2rem, --card-radius: 12px, --icon-size: 48px, --grid-gap: 2rem, --section-padding: 4rem 0, --hover-transform: translateY(-4px)"
23,Minimal & Direct,Landing Page,"Minimal text, white space heavy, single column layout, direct messaging, clean typography, visual-centric, fast-loading","Monochromatic primary, white background, single accent color for CTA, black/dark grey text","Minimal secondary colors, reserved for critical CTAs only, neutral supporting elements","Very subtle hover effects, minimal animations, fast page load (no heavy animations), smooth scroll","Simple service landing pages, indie products, consulting services, micro SaaS, freelancer portfolios","Feature-heavy products, complex explanations, multi-product showcases",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ Full,✓ High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a minimal direct landing page. Use: single column layout, maximum white space, essential content only, one CTA, clean typography, no decorative elements, fast loading, direct messaging.","max-width: 680px, margin: 0 auto, padding: 4rem 2rem, font-size: 18-20px, line-height: 1.6, minimal animations, no box-shadow, clean borders only","☐ Single column centered, â˜ White space generous, â˜ One primary CTA only, â˜ No decorative images, â˜ Page weight < 500KB, â˜ Load time < 2s","--content-max-width: 680px, --spacing-large: 4rem, --font-size-body: 18px, --line-height: 1.6, --color-text: #1a1a1a, --color-bg: #ffffff"
24,Social Proof-Focused,Landing Page,"Testimonials prominent, client logos displayed, case studies sections, reviews/ratings, user avatars, success metrics, credibility markers","Primary brand, trust colors (blue), success/growth colors (green), neutral backgrounds","Testimonial highlight colors, logo grid backgrounds (light grey), badge/achievement colors","Testimonial carousel animations, logo grid fade-in, stat counter animations (number count-up), review star ratings","B2B SaaS, professional services, premium products, e-commerce conversion pages, established brands","Startup MVPs, products without users, niche/experimental products",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,✓ Full,✓ High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a social proof landing page. Use: testimonials with photos, client logos grid, case study cards, review ratings (stars), user count metrics, success stories, trust indicators, before/after comparisons.","testimonial cards with avatar, logo grid (grayscale filter), star rating SVGs, counter animations (count-up), blockquote styling, carousel for testimonials, metric cards","☐ Testimonials with real photos, â˜ Logo grid 6-12 logos, â˜ Star ratings accessible, â˜ Metrics animated on scroll, â˜ Case studies linked, â˜ Mobile carousel works","--avatar-size: 64px, --logo-height: 40px, --star-color: #FBBF24, --metric-font-size: 3rem, --testimonial-bg: #F9FAFB, --blockquote-border: 4px solid accent"
25,Interactive Product Demo,Landing Page,"Embedded product mockup/video, interactive elements, product walkthrough, step-by-step guides, hover-to-reveal features, embedded demos","Primary brand, interface colors matching product, demo highlight colors for interactive elements","Product UI colors, tutorial step colors (numbered progression), hover state indicators","Product animation playback, step progression animations, hover reveal effects, smooth zoom on interaction","SaaS platforms, tool/software products, productivity apps landing pages, developer tools, productivity software","Simple services, consulting, non-digital products, complexity-averse audiences",✓ Full,✓ Full,⚠ Good (video/interactive),✓ WCAG AA,✓ Good,✓ Very High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design an interactive demo landing page. Use: embedded product mockup, video walkthrough, step-by-step guide, hover-to-reveal features, live demo button, screenshot carousel, feature highlights on interaction.","video element with controls, position: relative for overlays, hover reveal (opacity transition), step indicators, modal for full demo, screenshot lightbox, play button overlay","☐ Demo video loads fast, â˜ Fallback for no-JS, â˜ Step indicators clear, â˜ Hover states obvious, â˜ Mobile touch friendly, â˜ Demo CTA prominent","--video-aspect-ratio: 16/9, --overlay-bg: rgba(0,0,0,0.7), --step-indicator-size: 32px, --play-button-size: 80px, --transition-duration: 300ms"
26,Trust & Authority,Landing Page,"Certificates/badges displayed, expert credentials, case studies with metrics, before/after comparisons, industry recognition, security badges","Professional colors (blue/grey), trust colors, certification badge colors (gold/silver accents)","Certificate highlight colors, metric showcase colors, comparison highlight (success green)","Badge hover effects, metric pulse animations, certificate carousel, smooth stat reveal","Healthcare/medical landing pages, financial services, enterprise software, premium/luxury products, legal services","Casual products, entertainment, viral/social-first products",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ Full,✓ High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a trust-focused landing page. Use: certification badges, security indicators, expert credentials, industry awards, case study metrics, compliance logos (GDPR, SOC2), guarantee badges, professional photography.","badge grid layout, shield icons, lock icons for security, certificate styling, metric cards with icons, professional color scheme (blue/grey), subtle shadows for depth","☐ Security badges visible, â˜ Certifications verified, â˜ Metrics with sources, â˜ Professional imagery, â˜ Guarantee clearly stated, â˜ Contact info accessible","--badge-height: 48px, --trust-color: #1E40AF, --security-green: #059669, --card-shadow: 0 4px 6px rgba(0,0,0,0.1), --metric-highlight: #F59E0B"
27,Storytelling-Driven,Landing Page,"Narrative flow, visual story progression, section transitions, consistent character/brand voice, emotional messaging, journey visualization","Brand primary, warm/emotional colors, varied accent colors per story section, high visual variety","Story section color coding, emotional state colors (calm, excitement, success), transitional gradients","Section-to-section animations, scroll-triggered reveals, character/icon animations, morphing transitions, parallax narrative","Brand/startup stories, mission-driven products, premium/lifestyle brands, documentary-style products, educational","Technical/complex products (unless narrative-driven), traditional enterprise software",✓ Full,✓ Full,⚠ Moderate (animations),✓ WCAG AA,✓ Good,✓ High,"Tailwind 10/10, Bootstrap 9/10",2020s Modern,Medium,"Design a storytelling landing page. Use: narrative flow sections, scroll-triggered reveals, chapter-like structure, emotional imagery, brand journey visualization, founder story, mission statement, timeline progression.","scroll-snap sections, Intersection Observer for reveals, parallax backgrounds, section transitions, timeline CSS, narrative typography (varied sizes), image-text alternating","☐ Story flows naturally, â˜ Scroll reveals smooth, â˜ Sections timed well, â˜ Emotional hooks present, â˜ Mobile story readable, â˜ Skip option available","--section-min-height: 100vh, --reveal-duration: 600ms, --narrative-font: serif, --chapter-spacing: 8rem, --timeline-color: accent, --parallax-speed: 0.5"
28,Data-Dense Dashboard,BI/Analytics,"Multiple charts/widgets, data tables, KPI cards, minimal padding, grid layout, space-efficient, maximum data visibility","Neutral primary (light grey/white #F5F5F5), data colors (blue/green/red), dark text #333333","Chart colors: success (green #22C55E), warning (amber #F59E0B), alert (red #EF4444), neutral (grey)","Hover tooltips, chart zoom on click, row highlighting on hover, smooth filter animations, data loading spinners","Business intelligence dashboards, financial analytics, enterprise reporting, operational dashboards, data warehousing","Marketing dashboards, consumer-facing analytics, simple reporting",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a data-dense dashboard. Use: multiple chart widgets, KPI cards row, data tables with sorting, minimal padding (8-12px), efficient grid layout, filter sidebar, dense but readable typography, maximum information density.","display: grid, grid-template-columns: repeat(12, 1fr), gap: 8px, padding: 12px, font-size: 12-14px, overflow: auto for tables, compact card design, sticky headers","☐ Grid layout 12 columns, â˜ KPI cards responsive, â˜ Tables sortable, â˜ Filters functional, â˜ Loading states for data, â˜ Export functionality","--grid-gap: 8px, --card-padding: 12px, --font-size-small: 12px, --table-row-height: 36px, --sidebar-width: 240px, --header-height: 56px"
29,Heat Map & Heatmap Style,BI/Analytics,"Color-coded grid/matrix, data intensity visualization, geographical heat maps, correlation matrices, cell-based representation, gradient coloring","Gradient scale: Cool (blue #0080FF) to hot (red #FF0000), neutral middle (white/yellow)","Support gradients: Light (cool blue) to dark (warm red), divergent for positive/negative data, monochromatic options","Color gradient transitions on data change, cell highlighting on hover, tooltip reveal on click, smooth color animation","Geographical analysis, performance matrices, correlation analysis, user behavior heatmaps, temperature/intensity data","Linear data representation, categorical comparisons (use bar charts), small datasets",✓ Full,✓ Full (with adjustments),⚡ Excellent,⚠ Colorblind considerations,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a heatmap visualization. Use: color gradient scale (cool to hot), cell-based grid, intensity legend, hover tooltips, geographic or matrix layout, divergent color scheme for +/- values, accessible color alternatives.","display: grid, background: linear-gradient for legend, cell hover states, tooltip positioning, color scale (blue→white→red), SVG for geographic, canvas for large datasets","☐ Color scale clear, â˜ Legend visible, â˜ Tooltips informative, â˜ Colorblind alternatives, â˜ Zoom/pan for geo, â˜ Performance for large data","--heatmap-cool: #0080FF, --heatmap-neutral: #FFFFFF, --heatmap-hot: #FF0000, --cell-size: 24px, --legend-width: 200px, --tooltip-bg: rgba(0,0,0,0.9)"
30,Executive Dashboard,BI/Analytics,"High-level KPIs, large key metrics, minimal detail, summary view, trend indicators, at-a-glance insights, executive summary","Brand colors, professional palette (blue/grey/white), accent for KPIs, red for alerts/concerns","KPI highlight colors: positive (green), negative (red), neutral (grey), trend arrow colors","KPI value animations (count-up), trend arrow direction animations, metric card hover lift, alert pulse effect","C-suite dashboards, business summary reports, decision-maker dashboards, strategic planning views","Detailed analyst dashboards, technical deep-dives, operational monitoring",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✗ Low (not mobile-optimized),✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design an executive dashboard. Use: large KPI cards (4-6 max), trend sparklines, high-level summary only, clean layout with white space, traffic light indicators (red/yellow/green), at-a-glance insights, minimal detail.","display: flex for KPI row, large font-size (24-48px) for metrics, sparkline SVG inline, status indicators (border-left color), card shadows for hierarchy, responsive breakpoints","☐ KPIs 4-6 maximum, â˜ Trends visible, â˜ Status colors clear, â˜ One-page view, â˜ Mobile simplified, â˜ Print-friendly layout","--kpi-font-size: 48px, --sparkline-height: 32px, --status-green: #22C55E, --status-yellow: #F59E0B, --status-red: #EF4444, --card-min-width: 280px"
31,Real-Time Monitoring,BI/Analytics,"Live data updates, status indicators, alert notifications, streaming data visualization, active monitoring, streaming charts","Alert colors: critical (red #FF0000), warning (orange #FFA500), normal (green #22C55E), updating (blue animation)","Status indicator colors, chart line colors varying by metric, streaming data highlight colors","Real-time chart animations, alert pulse/glow, status indicator blink animation, smooth data stream updates, loading effect","System monitoring dashboards, DevOps dashboards, real-time analytics, stock market dashboards, live event tracking","Historical analysis, long-term trend reports, archived data dashboards",✓ Full,✓ Full,⚡ Good (real-time load),✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a real-time monitoring dashboard. Use: live status indicators (pulsing), streaming charts, alert notifications, connection status, auto-refresh indicators, critical alerts prominent, system health overview.","animation: pulse for live, WebSocket for streaming, position: fixed for alerts, status-dot with animation, chart real-time updates, notification toast, connection indicator","☐ Live updates working, â˜ Alert sounds optional, â˜ Connection status shown, â˜ Auto-refresh indicated, â˜ Critical alerts prominent, â˜ Offline fallback","--pulse-animation: pulse 2s infinite, --alert-z-index: 1000, --live-indicator: #22C55E, --critical-color: #DC2626, --update-interval: 5s, --toast-duration: 5s"
32,Drill-Down Analytics,BI/Analytics,"Hierarchical data exploration, expandable sections, interactive drill-down paths, summary-to-detail flow, context preservation","Primary brand, breadcrumb colors, drill-level indicator colors, hierarchy depth colors","Drill-down path indicator colors, level-specific colors, highlight colors for selected level, transition colors","Drill-down expand animations, breadcrumb click transitions, smooth detail reveal, level change smooth, data reload animation","Sales analytics, product analytics, funnel analysis, multi-dimensional data exploration, business intelligence","Simple linear data, single-metric dashboards, streaming real-time dashboards",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a drill-down analytics dashboard. Use: breadcrumb navigation, expandable sections, summary-to-detail flow, back button prominent, level indicators, context preservation, hierarchical data display.","breadcrumb nav with separators, details/summary for expand, transition for drill animation, position: sticky breadcrumb, nested grid layouts, smooth scroll to detail","☐ Breadcrumbs clear, â˜ Back navigation easy, â˜ Expand animation smooth, â˜ Context preserved, â˜ Mobile drill works, â˜ Deep links supported","--breadcrumb-separator: /, --expand-duration: 300ms, --level-indent: 24px, --back-button-size: 40px, --context-bar-height: 48px, --drill-transition: 300ms ease"
33,Comparative Analysis Dashboard,BI/Analytics,"Side-by-side comparisons, period-over-period metrics, A/B test results, regional comparisons, performance benchmarks","Comparison colors: primary (blue), comparison (orange/purple), delta indicator (green/red)","Winning metric color (green), losing metric color (red), neutral comparison (grey), benchmark colors","Comparison bar animations (grow to value), delta indicator animations (direction arrows), highlight on compare","Period-over-period reporting, A/B test dashboards, market comparison, competitive analysis, regional performance","Single metric dashboards, future projections (use forecasting), real-time only (no historical)",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a comparison dashboard. Use: side-by-side metrics, period selectors (vs last month), delta indicators (+/-), benchmark lines, A/B comparison tables, winning/losing highlights, percentage change badges.","display: flex for side-by-side, gap for comparison spacing, color coding (green up, red down), arrow indicators, diff highlighting, comparison table zebra striping","☐ Period selector works, â˜ Deltas calculated, â˜ Colors meaningful, â˜ Benchmarks shown, â˜ Mobile stacks properly, â˜ Export comparison","--positive-color: #22C55E, --negative-color: #EF4444, --neutral-color: #6B7280, --comparison-gap: 2rem, --arrow-size: 16px, --badge-padding: 4px 8px"
34,Predictive Analytics,BI/Analytics,"Forecast lines, confidence intervals, trend projections, scenario modeling, AI-driven insights, anomaly detection visualization","Forecast line color (distinct from actual), confidence interval shading, anomaly highlight (red alert), trend colors","High confidence (dark color), low confidence (light color), anomaly colors (red/orange), normal trend (green/blue)","Forecast line animation on draw, confidence band fade-in, anomaly pulse alert, smoothing function animations","Forecasting dashboards, anomaly detection systems, trend prediction dashboards, AI-powered analytics, budget planning","Historical-only dashboards, simple reporting, real-time operational dashboards",✓ Full,✓ Full,⚠ Good (computation),✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a predictive analytics dashboard. Use: forecast lines (dashed), confidence intervals (shaded bands), trend projections, anomaly highlights, scenario toggles, AI insight cards, probability indicators.","stroke-dasharray for forecast lines, fill-opacity for confidence bands, anomaly markers (circles), tooltip for predictions, toggle switches for scenarios, gradient for probability","☐ Forecast line distinct, â˜ Confidence bands visible, â˜ Anomalies highlighted, â˜ Scenarios switchable, â˜ Predictions dated, â˜ Accuracy shown","--forecast-dash: 5 5, --confidence-opacity: 0.2, --anomaly-color: #F59E0B, --prediction-color: #8B5CF6, --scenario-toggle-width: 48px, --ai-accent: #6366F1"
35,User Behavior Analytics,BI/Analytics,"Funnel visualization, user flow diagrams, conversion tracking, engagement metrics, user journey mapping, cohort analysis","Funnel stage colors: high engagement (green), drop-off (red), conversion (blue), user flow arrows (grey)","Stage completion colors (success), abandonment colors (warning), engagement levels (gradient), cohort colors","Funnel animation (fill-down), flow diagram animations (connection draw), conversion pulse, engagement bar fill","Conversion funnel analysis, user journey tracking, engagement analytics, cohort analysis, retention tracking","Real-time operational metrics, technical system monitoring, financial transactions",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,✓ Good,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a user behavior analytics dashboard. Use: funnel visualization, user flow diagrams (Sankey), conversion metrics, engagement heatmaps, cohort tables, retention curves, session replay indicators.","SVG funnel with gradients, Sankey diagram library, percentage labels, cohort grid cells, retention chart (line/area), click heatmap overlay, session timeline","☐ Funnel stages clear, â˜ Flow diagram readable, â˜ Conversions calculated, â˜ Cohorts comparable, â˜ Retention trends visible, â˜ Privacy compliant","--funnel-width: 100%, --stage-colors: gradient, --flow-opacity: 0.6, --cohort-cell-size: 40px, --retention-line-color: #3B82F6, --engagement-scale: 5 levels"
36,Financial Dashboard,BI/Analytics,"Revenue metrics, profit/loss visualization, budget tracking, financial ratios, portfolio performance, cash flow, audit trail","Financial colors: profit (green #22C55E), loss (red #EF4444), neutral (grey), trust (dark blue #003366)","Revenue highlight (green), expenses (red), budget variance (orange/red), balance (grey), accuracy (blue)","Number animations (count-up), trend direction indicators, percentage change animations, profit/loss color transitions","Financial reporting, accounting dashboards, portfolio tracking, budget monitoring, banking analytics","Simple business dashboards, entertainment/social metrics, non-financial data",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✗ Low,✗ Not applicable,"Recharts 9/10, Chart.js 9/10, D3.js 10/10",2020s Modern,Medium,"Design a financial dashboard. Use: revenue/expense charts, profit margins, budget vs actual, cash flow waterfall, financial ratios, audit trail table, currency formatting, period comparisons.","number formatting (Intl.NumberFormat), waterfall chart (positive/negative bars), variance coloring, table with totals row, sparkline for trends, sticky column headers","☐ Currency formatted, â˜ Decimals consistent, â˜ P&L clear, â˜ Budget variance shown, â˜ Audit trail complete, â˜ Export to Excel","--currency-symbol: $, --decimal-places: 2, --profit-color: #22C55E, --loss-color: #EF4444, --variance-threshold: 10%, --table-header-bg: #F3F4F6"
37,Sales Intelligence Dashboard,BI/Analytics,"Deal pipeline, sales metrics, territory performance, sales rep leaderboard, win-loss analysis, quota tracking, forecast accuracy","Sales colors: won (green), lost (red), in-progress (blue), blocked (orange), quota met (gold), quota missed (grey)","Pipeline stage colors, rep performance colors, quota achievement colors, forecast accuracy colors","Deal movement animations, metric updates, leaderboard ranking changes, gauge needle movements, status change highlights","CRM dashboards, sales management, opportunity tracking, performance management, quota planning","Marketing analytics, customer support metrics, HR dashboards",✓ Full,✓ Full,⚡ Good,✓ WCAG AA,◐ Medium,✗ Not applicable,"Recharts 9/10, Chart.js 9/10",2020s Modern,Medium,"Design a sales intelligence dashboard. Use: pipeline funnel, deal cards (kanban), quota gauges, leaderboard table, territory map, win/loss ratios, forecast accuracy, activity timeline.","kanban columns (flex), gauge chart (SVG arc), leaderboard ranking styles, map integration (Mapbox/Google), timeline vertical, deal card with status border","☐ Pipeline stages shown, â˜ Deals draggable, â˜ Quotas visualized, â˜ Rankings updated, â˜ Territory clickable, â˜ CRM integration","--pipeline-colors: stage gradient, --gauge-track: #E5E7EB, --gauge-fill: primary, --rank-1-color: #FFD700, --rank-2-color: #C0C0C0, --rank-3-color: #CD7F32"
38,Neubrutalism,General,"Bold borders, black outlines, primary colors, thick shadows, no gradients, flat colors, 45° shadows, playful, Gen Z","#FFEB3B (Yellow), #FF5252 (Red), #2196F3 (Blue), #000000 (Black borders)","Limited accent colors, high contrast combinations, no gradients allowed","box-shadow: 4px 4px 0 #000, border: 3px solid #000, no gradients, sharp corners (0px), bold typography","Gen Z brands, startups, creative agencies, Figma-style apps, Notion-style interfaces, tech blogs","Luxury brands, finance, healthcare, conservative industries (too playful)",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ High,"Tailwind 10/10, Bootstrap 8/10",2020s Modern,Low,"Design a neubrutalist interface. Use: high contrast, hard black borders (3px+), bright pop colors, no blur, sharp or slightly rounded corners, bold typography, hard shadows (offset 4px 4px), raw aesthetic but functional.","border: 3px solid black, box-shadow: 5px 5px 0px black, colors: #FFDB58 #FF6B6B #4ECDC4, font-weight: 700, no gradients","☐ Hard borders (2-4px), â˜ Hard offset shadows, â˜ High saturation colors, â˜ Bold typography, â˜ No blurs/gradients, â˜ Distinctive 'ugly-cute' look","--border-width: 3px, --shadow-offset: 4px, --shadow-color: #000, --colors: high saturation, --font: bold sans"
39,Bento Box Grid,General,"Modular cards, asymmetric grid, varied sizes, Apple-style, dashboard tiles, negative space, clean hierarchy, cards","Neutral base + brand accent, #FFFFFF, #F5F5F5, brand primary","Subtle gradients, shadow variations, accent highlights for interactive cards","grid-template with varied spans, rounded-xl (16px), subtle shadows, hover scale (1.02), smooth transitions","Dashboards, product pages, portfolios, Apple-style marketing, feature showcases, SaaS","Dense data tables, text-heavy content, real-time monitoring",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"Tailwind 10/10, CSS Grid 10/10",2020s Apple,Low,"Design a Bento Box grid layout. Use: modular cards with varied sizes (1x1, 2x1, 2x2), Apple-style aesthetic, rounded corners (16-24px), soft shadows, clean hierarchy, asymmetric grid, neutral backgrounds (#F5F5F7), hover effects.","display: grid, grid-template-columns: repeat(4, 1fr), grid-auto-rows: 200px, gap: 16px, border-radius: 24px, background: #FFFFFF, box-shadow: 0 4px 6px rgba(0,0,0,0.05)","☐ Grid responsive (4→2→1 cols), â˜ Card spans varied, â˜ Rounded corners consistent, â˜ Shadows subtle, â˜ Content fits cards, â˜ Hover scale (1.02)","--grid-gap: 16px, --card-radius: 24px, --card-bg: #FFFFFF, --page-bg: #F5F5F7, --shadow: 0 4px 6px rgba(0,0,0,0.05), --hover-scale: 1.02"
40,Y2K Aesthetic,General,"Neon pink, chrome, metallic, bubblegum, iridescent, glossy, retro-futurism, 2000s, futuristic nostalgia","#FF69B4 (Hot Pink), #00FFFF (Cyan), #C0C0C0 (Silver), #9400D3 (Purple)","Metallic gradients, glossy overlays, iridescent effects, chrome textures","linear-gradient metallic, glossy buttons, 3D chrome effects, glow animations, bubble shapes","Fashion brands, music platforms, Gen Z brands, nostalgia marketing, entertainment, youth-focused","B2B enterprise, healthcare, finance, conservative industries, elderly users",✓ Full,◐ Partial,⚠ Good,⚠ Check contrast,✓ Good,✓ High,"Tailwind 8/10, CSS-in-JS 9/10",Y2K 2000s,Medium,"Design a Y2K aesthetic interface. Use: neon pink/cyan colors, chrome/metallic textures, bubblegum gradients, glossy buttons, iridescent effects, 2000s futurism, star/sparkle decorations, bubble shapes, tech-optimistic vibe.","background: linear-gradient(135deg, #FF69B4, #00FFFF), filter: drop-shadow for glow, border-radius: 50% for bubbles, metallic gradients (silver/chrome), text-shadow: neon glow, ::before for sparkles","☐ Neon colors balanced, â˜ Chrome effects visible, â˜ Glossy buttons styled, â˜ Bubble shapes decorative, â˜ Sparkle animations, â˜ Retro fonts loaded","--neon-pink: #FF69B4, --neon-cyan: #00FFFF, --chrome-silver: #C0C0C0, --glossy-gradient: linear-gradient(180deg, white 0%, transparent 50%), --glow-blur: 10px"
41,Cyberpunk UI,General,"Neon, dark mode, terminal, HUD, sci-fi, glitch, dystopian, futuristic, matrix, tech noir","#00FF00 (Matrix Green), #FF00FF (Magenta), #00FFFF (Cyan), #0D0D0D (Dark)","Neon gradients, scanline overlays, glitch colors, terminal green accents","Neon glow (text-shadow), glitch animations (skew/offset), scanlines (::before overlay), terminal fonts","Gaming platforms, tech products, crypto apps, sci-fi applications, developer tools, entertainment","Corporate enterprise, healthcare, family apps, conservative brands, elderly users",✗ No,✓ Only,⚠ Moderate,⚠ Limited (dark+neon),◐ Medium,◐ Medium,"Tailwind 8/10, Custom CSS 10/10",2020s Cyberpunk,Medium,"Design a cyberpunk interface. Use: neon colors on dark (#0D0D0D), terminal/HUD aesthetic, glitch effects, scanlines overlay, matrix green accents, monospace fonts, angular shapes, dystopian tech feel.","background: #0D0D0D, color: #00FF00 or #FF00FF, font-family: monospace, text-shadow: 0 0 10px neon, animation: glitch (transform skew), ::before scanlines (repeating-linear-gradient)","☐ Dark background only, â˜ Neon accents visible, â˜ Glitch effect subtle, â˜ Scanlines optional, â˜ Monospace font, â˜ Terminal aesthetic","--bg-dark: #0D0D0D, --neon-green: #00FF00, --neon-magenta: #FF00FF, --neon-cyan: #00FFFF, --scanline-opacity: 0.1, --glitch-duration: 0.3s"
42,Organic Biophilic,General,"Nature, organic shapes, green, sustainable, rounded, flowing, wellness, earthy, natural textures","#228B22 (Forest Green), #8B4513 (Earth Brown), #87CEEB (Sky Blue), #F5F5DC (Beige)","Natural gradients, earth tones, sky blues, organic textures, wood/stone colors","Rounded corners (16-24px), organic curves (border-radius variations), natural shadows, flowing SVG shapes","Wellness apps, sustainability brands, eco products, health apps, meditation, organic food brands","Tech-focused products, gaming, industrial, urban brands",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"Tailwind 10/10, CSS 10/10",2020s Sustainable,Low,"Design a biophilic organic interface. Use: nature-inspired colors (greens, browns), organic curved shapes, rounded corners (16-24px), natural textures (wood, stone), flowing SVG elements, wellness aesthetic, earthy palette.","border-radius: 16-24px (varied), background: earth tones, SVG organic shapes (blob), box-shadow: natural soft, color: #228B22 #8B4513 #87CEEB, texture overlays (subtle)","☐ Earth tones dominant, â˜ Organic curves present, â˜ Natural textures subtle, â˜ Green accents, â˜ Rounded everywhere, â˜ Calming feel","--forest-green: #228B22, --earth-brown: #8B4513, --sky-blue: #87CEEB, --cream-bg: #F5F5DC, --organic-radius: 24px, --shadow-soft: 0 8px 32px rgba(0,0,0,0.08)"
43,AI-Native UI,General,"Chatbot, conversational, voice, assistant, agentic, ambient, minimal chrome, streaming text, AI interactions","Neutral + single accent, #6366F1 (AI Purple), #10B981 (Success), #F5F5F5 (Background)","Status indicators, streaming highlights, context card colors, subtle accent variations","Typing indicators (3-dot pulse), streaming text animations, pulse animations, context cards, smooth reveals","AI products, chatbots, voice assistants, copilots, AI-powered tools, conversational interfaces","Traditional forms, data-heavy dashboards, print-first content",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"Tailwind 10/10, React 10/10",2020s AI-Era,Low,"Design an AI-native interface. Use: minimal chrome, conversational layout, streaming text area, typing indicators (3-dot pulse), context cards, subtle AI accent color (#6366F1), clean input field, response bubbles.","chat bubble layout (flex-direction: column), typing animation (3 dots pulse), streaming text (overflow: hidden + animation), input: sticky bottom, context cards (border-left accent), minimal borders","☐ Chat layout responsive, â˜ Typing indicator smooth, â˜ Input always visible, â˜ Context cards styled, â˜ AI responses distinct, â˜ User messages aligned right","--ai-accent: #6366F1, --user-bubble-bg: #E0E7FF, --ai-bubble-bg: #F9FAFB, --input-height: 48px, --typing-dot-size: 8px, --message-gap: 16px"
44,Memphis Design,General,"80s, geometric, playful, postmodern, shapes, patterns, squiggles, triangles, neon, abstract, bold","#FF71CE (Hot Pink), #FFCE5C (Yellow), #86CCCA (Teal), #6A7BB4 (Blue Purple)","Complementary geometric colors, pattern fills, contrasting accent shapes","transform: rotate(), clip-path: polygon(), mix-blend-mode, repeating patterns, bold shapes","Creative agencies, music sites, youth brands, event promotion, artistic portfolios, entertainment","Corporate finance, healthcare, legal, elderly users, conservative brands",✓ Full,✓ Full,⚡ Excellent,⚠ Check contrast,✓ Good,◐ Medium,"Tailwind 9/10, CSS 10/10",1980s Postmodern,Medium,"Design a Memphis style interface. Use: bold geometric shapes (triangles, squiggles, circles), bright clashing colors, 80s postmodern aesthetic, playful patterns, dotted textures, asymmetric layouts, decorative elements.","clip-path: polygon() for shapes, background: repeating patterns, transform: rotate() for tilted elements, mix-blend-mode for overlays, border: dashed/dotted patterns, bold sans-serif","☐ Geometric shapes visible, â˜ Colors bold/clashing, â˜ Patterns present, â˜ Layout asymmetric, â˜ Playful decorations, â˜ 80s vibe achieved","--memphis-pink: #FF71CE, --memphis-yellow: #FFCE5C, --memphis-teal: #86CCCA, --memphis-purple: #6A7BB4, --pattern-size: 20px, --shape-rotation: 15deg"
45,Vaporwave,General,"Synthwave, retro-futuristic, 80s-90s, neon, glitch, nostalgic, sunset gradient, dreamy, aesthetic","#FF71CE (Pink), #01CDFE (Cyan), #05FFA1 (Mint), #B967FF (Purple)","Sunset gradients, glitch overlays, VHS effects, neon accents, pastel variations","text-shadow glow, linear-gradient, filter: hue-rotate(), glitch animations, retro scan lines","Music platforms, gaming, creative portfolios, tech startups, entertainment, artistic projects","Business apps, e-commerce, education, healthcare, enterprise software",✓ Full,✓ Dark focused,⚠ Moderate,⚠ Poor (motion),◐ Medium,◐ Medium,"Tailwind 8/10, CSS-in-JS 9/10",1980s-90s Retro,Medium,"Design a vaporwave aesthetic interface. Use: sunset gradients (pink/cyan/purple), 80s-90s nostalgia, glitch effects, Greek statue imagery, palm trees, grid patterns, neon glow, retro-futuristic feel, dreamy atmosphere.","background: linear-gradient(180deg, #FF71CE, #01CDFE, #B967FF), filter: hue-rotate(), text-shadow: neon glow, retro grid (perspective + linear-gradient), VHS scanlines","☐ Sunset gradient present, â˜ Neon glow applied, â˜ Retro grid visible, â˜ Glitch effects subtle, â˜ Dreamy atmosphere, â˜ 80s-90s aesthetic","--vapor-pink: #FF71CE, --vapor-cyan: #01CDFE, --vapor-mint: #05FFA1, --vapor-purple: #B967FF, --grid-color: rgba(255,255,255,0.1), --glow-intensity: 15px"
46,Dimensional Layering,General,"Depth, overlapping, z-index, layers, 3D, shadows, elevation, floating, cards, spatial hierarchy","Neutral base (#FFFFFF, #F5F5F5, #E0E0E0) + brand accent for elevated elements","Shadow variations (sm/md/lg/xl), elevation colors, highlight colors for top layers","z-index stacking, box-shadow elevation (4 levels), transform: translateZ(), backdrop-filter, parallax","Dashboards, card layouts, modals, navigation, product showcases, SaaS interfaces","Print-style layouts, simple blogs, low-end devices, flat design requirements",✓ Full,✓ Full,⚠ Good,⚠ Moderate (SR issues),✓ Good,✓ High,"Tailwind 10/10, MUI 10/10, Chakra 10/10",2020s Modern,Medium,"Design with dimensional layering. Use: z-index depth (multiple layers), overlapping cards, elevation shadows (4 levels), floating elements, parallax depth, backdrop blur for hierarchy, spatial UI feel.","z-index: 1-4 levels, box-shadow: elevation scale (sm/md/lg/xl), transform: translateZ(), backdrop-filter: blur(), position: relative for stacking, parallax on scroll","☐ Layers clearly defined, â˜ Shadows show depth, â˜ Overlaps intentional, â˜ Hierarchy clear, â˜ Performance optimized, â˜ Mobile depth maintained","--elevation-1: 0 1px 3px rgba(0,0,0,0.1), --elevation-2: 0 4px 6px rgba(0,0,0,0.1), --elevation-3: 0 10px 20px rgba(0,0,0,0.1), --elevation-4: 0 20px 40px rgba(0,0,0,0.15), --blur-amount: 8px"
47,Exaggerated Minimalism,General,"Bold minimalism, oversized typography, high contrast, negative space, loud minimal, statement design","#000000 (Black), #FFFFFF (White), single vibrant accent only","Minimal - single accent color, no secondary colors, extreme restraint","font-size: clamp(3rem 10vw 12rem), font-weight: 900, letter-spacing: -0.05em, massive whitespace","Fashion, architecture, portfolios, agency landing pages, luxury brands, editorial","E-commerce catalogs, dashboards, forms, data-heavy, elderly users, complex apps",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"Tailwind 10/10, Typography.js 10/10",2020s Modern,Low,"Design with exaggerated minimalism. Use: oversized typography (clamp 3rem-12rem), extreme negative space, black/white primary, single accent color only, bold statements, minimal elements, dramatic contrast.","font-size: clamp(3rem, 10vw, 12rem), font-weight: 900, letter-spacing: -0.05em, color: #000 or #FFF, padding: 8rem+, single accent, no decorations","☐ Typography oversized, â˜ White space extreme, â˜ Black/white dominant, â˜ Single accent only, â˜ Elements minimal, â˜ Statement clear","--type-giant: clamp(3rem, 10vw, 12rem), --type-weight: 900, --spacing-huge: 8rem, --color-primary: #000000, --color-bg: #FFFFFF, --accent: single color only"
48,Kinetic Typography,General,"Motion text, animated type, moving letters, dynamic, typing effect, morphing, scroll-triggered text","Flexible - high contrast recommended, bold colors for emphasis, animation-friendly palette","Accent colors for emphasis, transition colors, gradient text fills","@keyframes text animation, typing effect, background-clip: text, GSAP ScrollTrigger, split text","Hero sections, marketing sites, video platforms, storytelling, creative portfolios, landing pages","Long-form content, accessibility-critical, data interfaces, forms, elderly users",✓ Full,✓ Full,⚠ Moderate,❌ Poor (motion),✓ Good,✓ Very High,"GSAP 10/10, Framer Motion 10/10",2020s Modern,High,"Design with kinetic typography. Use: animated text, scroll-triggered reveals, typing effects, letter-by-letter animations, morphing text, gradient text fills, oversized hero text, text as the main visual element.","@keyframes for text animation, background-clip: text, GSAP SplitText, typing effect (steps()), transform on letters, scroll-triggered (Intersection Observer), variable fonts for morphing","☐ Text animations smooth, â˜ Prefers-reduced-motion respected, â˜ Fallback for no-JS, â˜ Mobile performance ok, â˜ Typing effect timed, â˜ Scroll triggers work","--text-animation-duration: 1s, --letter-delay: 0.05s, --typing-speed: 100ms, --gradient-text: linear-gradient(90deg, #color1, #color2), --morph-duration: 0.5s"
49,Parallax Storytelling,General,"Scroll-driven, narrative, layered scrolling, immersive, progressive disclosure, cinematic, scroll-triggered","Story-dependent, often gradients and natural colors, section-specific palettes","Section transition colors, depth layer colors, narrative mood colors","transform: translateY(scroll), position: fixed/sticky, perspective: 1px, scroll-triggered animations","Brand storytelling, product launches, case studies, portfolios, annual reports, marketing campaigns","E-commerce, dashboards, mobile-first, SEO-critical, accessibility-required",✓ Full,✓ Full,❌ Poor,❌ Poor (motion),✗ Low,✓ High,"GSAP ScrollTrigger 10/10, Locomotive Scroll 10/10",2020s Modern,High,"Design a parallax storytelling page. Use: scroll-driven narrative, layered backgrounds (3-5 layers), fixed/sticky sections, cinematic transitions, progressive disclosure, full-screen chapters, depth perception.","position: fixed/sticky, transform: translateY(calc()), perspective: 1px, z-index layering, scroll-snap-type, Intersection Observer for triggers, will-change: transform","☐ Layers parallax smoothly, â˜ Story flows naturally, â˜ Mobile alternative provided, â˜ Performance optimized, â˜ Skip option available, â˜ Reduced motion fallback","--parallax-speed-bg: 0.3, --parallax-speed-mid: 0.6, --parallax-speed-fg: 1, --section-height: 100vh, --transition-duration: 600ms, --perspective: 1px"
50,Swiss Modernism 2.0,General,"Grid system, Helvetica, modular, asymmetric, international style, rational, clean, mathematical spacing","#000000, #FFFFFF, #F5F5F5, single vibrant accent only","Minimal secondary, accent for emphasis only, no gradients","display: grid, grid-template-columns: repeat(12 1fr), gap: 1rem, mathematical ratios, clear hierarchy","Corporate sites, architecture, editorial, SaaS, museums, professional services, documentation","Playful brands, children's sites, entertainment, gaming, emotional storytelling",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ High,"Tailwind 10/10, Bootstrap 9/10, Foundation 10/10",1950s Swiss + 2020s,Low,"Design with Swiss Modernism 2.0. Use: strict grid system (12 columns), Helvetica/Inter fonts, mathematical spacing, asymmetric balance, high contrast, minimal decoration, clean hierarchy, single accent color.","display: grid, grid-template-columns: repeat(12, 1fr), gap: 1rem (8px base unit), font-family: Inter/Helvetica, font-weight: 400-700, color: #000/#FFF, single accent","☐ 12-column grid strict, â˜ Spacing mathematical, â˜ Typography hierarchy clear, â˜ Single accent only, â˜ No decorations, â˜ High contrast verified","--grid-columns: 12, --grid-gap: 1rem, --base-unit: 8px, --font-primary: Inter, --color-text: #000000, --color-bg: #FFFFFF, --accent: single vibrant"
51,HUD / Sci-Fi FUI,General,"Futuristic, technical, wireframe, neon, data, transparency, iron man, sci-fi, interface","Neon Cyan #00FFFF, Holographic Blue #0080FF, Alert Red #FF0000","Transparent Black, Grid Lines #333333","Glow effects, scanning animations, ticker text, blinking markers, fine line drawing","Sci-fi games, space tech, cybersecurity, movie props, immersive dashboards","Standard corporate, reading heavy content, accessible public services",✓ Low,✓ Full,⚠ Moderate (renders),⚠ Poor (thin lines),◐ Medium,✗ Low,"React 9/10, Canvas 10/10",2010s Sci-Fi,High,"Design a futuristic HUD (Heads Up Display) or FUI. Use: thin lines (1px), neon cyan/blue on black, technical markers, decorative brackets, data visualization, monospaced tech fonts, glowing elements, transparency.","border: 1px solid rgba(0,255,255,0.5), color: #00FFFF, background: transparent or rgba(0,0,0,0.8), font-family: monospace, text-shadow: 0 0 5px cyan","☐ Fine lines 1px, â˜ Neon glow text/borders, â˜ Monospaced font, â˜ Dark/Transparent BG, â˜ Decorative tech markers, â˜ Holographic feel","--hud-color: #00FFFF, --bg-color: rgba(0,10,20,0.9), --line-width: 1px, --glow: 0 0 5px, --font: monospace"
52,Pixel Art,General,"Retro, 8-bit, 16-bit, gaming, blocky, nostalgic, pixelated, arcade","Primary colors (NES Palette), brights, limited palette","Black outlines, shading via dithering or block colors","Frame-by-frame sprite animation, blinking cursor, instant transitions, marquee text","Indie games, retro tools, creative portfolios, nostalgia marketing, Web3/NFT","Professional corporate, modern SaaS, high-res photography sites",✓ Full,✓ Full,⚡ Excellent,✓ Good (if contrast ok),✓ High,◐ Medium,"CSS (box-shadow) 8/10, Canvas 10/10",1980s Arcade,Medium,"Design a pixel art inspired interface. Use: pixelated fonts, 8-bit or 16-bit aesthetic, sharp edges (image-rendering: pixelated), limited color palette, blocky UI elements, retro gaming feel.","font-family: 'Press Start 2P', image-rendering: pixelated, box-shadow: 4px 0 0 #000 (pixel border), no anti-aliasing","☐ Pixelated fonts loaded, â˜ Images sharp (no blur), â˜ CSS box-shadow for pixel borders, â˜ Retro palette, â˜ Blocky layout","--pixel-size: 4px, --font: pixel font, --border-style: pixel-shadow, --anti-alias: none"
53,Bento Grids,General,"Apple-style, modular, cards, organized, clean, hierarchy, grid, rounded, soft","Off-white #F5F5F7, Clean White #FFFFFF, Text #1D1D1F","Subtle accents, soft shadows, blurred backdrops","Hover scale (1.02), soft shadow expansion, smooth layout shifts, content reveal","Product features, dashboards, personal sites, marketing summaries, galleries","Long-form reading, data tables, complex forms",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"CSS Grid 10/10, Tailwind 10/10",2020s Apple/Linear,Low,"Design a Bento Grid layout. Use: modular grid system, rounded corners (16-24px), different card sizes (1x1, 2x1, 2x2), card-based hierarchy, soft backgrounds (#F5F5F7), subtle borders, content-first, Apple-style aesthetic.","display: grid, grid-template-columns: repeat(auto-fit, minmax(...)), gap: 1rem, border-radius: 20px, background: #FFF, box-shadow: subtle","☐ Grid layout (CSS Grid), â˜ Rounded corners 16-24px, â˜ Varied card spans, â˜ Content fits card size, â˜ Responsive re-flow, â˜ Apple-like aesthetic","--grid-gap: 20px, --card-radius: 24px, --card-bg: #FFFFFF, --page-bg: #F5F5F7, --shadow: soft"
55,Spatial UI (VisionOS),General,"Glass, depth, immersion, spatial, translucent, gaze, gesture, apple, vision-pro","Frosted Glass #FFFFFF (15-30% opacity), System White","Vibrant system colors for active states, deep shadows for depth","Parallax depth, dynamic lighting response, gaze-hover effects, smooth scale on focus","Spatial computing apps, VR/AR interfaces, immersive media, futuristic dashboards","Text-heavy documents, high-contrast requirements, non-3D capable devices",✓ Full,✓ Full,⚠ Moderate (blur cost),⚠ Contrast risks,✓ High (if adapted),✓ High,"SwiftUI, React (Three.js/Fiber)",2024 Spatial Era,High,"Design a VisionOS-style spatial interface. Use: frosted glass panels, depth layers, translucent backgrounds (15-30% opacity), vibrant colors for active states, gaze-hover effects, floating windows, immersive feel.","backdrop-filter: blur(40px) saturate(180%), background: rgba(255,255,255,0.2), border-radius: 24px, box-shadow: 0 8px 32px rgba(0,0,0,0.1), transform: scale on focus, depth via shadows","☐ Glass effect visible, â˜ Depth layers clear, â˜ Hover states defined, â˜ Colors vibrant on active, â˜ Floating feel achieved, â˜ Contrast maintained","--glass-bg: rgba(255,255,255,0.2), --glass-blur: 40px, --glass-saturate: 180%, --window-radius: 24px, --depth-shadow: 0 8px 32px rgba(0,0,0,0.1), --focus-scale: 1.02"
56,E-Ink / Paper,General,"Paper-like, matte, high contrast, texture, reading, calm, slow tech, monochrome","Off-White #FDFBF7, Paper White #F5F5F5, Ink Black #1A1A1A","Pencil Grey #4A4A4A, Highlighter Yellow #FFFF00 (accent)","No motion blur, distinct page turns, grain/noise texture, sharp transitions (no fade)","Reading apps, digital newspapers, minimal journals, distraction-free writing, slow-living brands","Gaming, video platforms, high-energy marketing, dark mode dependent apps",✓ Full,✗ Low (inverted only),⚡ Excellent,✓ WCAG AAA,✓ High,✓ Medium,"Tailwind 10/10, CSS 10/10",2020s Digital Well-being,Low,"Design an e-ink/paper style interface. Use: high contrast black on off-white, paper texture, no animations (instant transitions), reading-focused, minimal UI chrome, distraction-free, calm aesthetic, monochrome.","background: #FDFBF7 (paper white), color: #1A1A1A, transition: none, font-family: serif for reading, no gradients, border: 1px solid #E0E0E0, texture overlay (noise)","☐ Paper background color, â˜ High contrast text, â˜ No animations, â˜ Reading optimized, â˜ Distraction-free, â˜ Print-friendly","--paper-bg: #FDFBF7, --ink-color: #1A1A1A, --pencil-grey: #4A4A4A, --border-color: #E0E0E0, --font-reading: Georgia, --transition: none"
57,Gen Z Chaos / Maximalism,General,"Chaos, clutter, stickers, raw, collage, mixed media, loud, internet culture, ironic","Clashing Brights: #FF00FF, #00FF00, #FFFF00, #0000FF","Gradients, rainbow, glitch, noise, heavily saturated mix","Marquee scrolls, jitter, sticker layering, GIF overload, random placement, drag-and-drop","Gen Z lifestyle brands, music artists, creative portfolios, viral marketing, fashion","Corporate, government, healthcare, banking, serious tools",✓ Full,✓ Full,⚠ Poor (heavy assets),❌ Poor,◐ Medium,✓ High (Viral),CSS-in-JS 8/10,2023+ Internet Core,High,"Design a Gen Z chaos maximalist interface. Use: clashing bright colors, sticker overlays, collage aesthetic, raw/unpolished feel, mixed media, ironic elements, loud typography, GIF-heavy, internet culture references.","mix-blend-mode: multiply/screen, transform: rotate(random), animation: jitter, marquee text, position: absolute for scattered elements, filter: saturate(150%), z-index chaos","☐ Colors clash intentionally, â˜ Stickers/overlays present, â˜ Layout chaotic but usable, â˜ GIFs optimized, â˜ Mobile scrollable, â˜ Performance acceptable","--chaos-pink: #FF00FF, --chaos-green: #00FF00, --chaos-yellow: #FFFF00, --chaos-blue: #0000FF, --jitter-amount: 5deg, --saturate: 150%"
58,Biomimetic / Organic 2.0,General,"Nature-inspired, cellular, fluid, breathing, generative, algorithms, life-like","Cellular Pink #FF9999, Chlorophyll Green #00FF41, Bioluminescent Blue","Deep Ocean #001E3C, Coral #FF7F50, Organic gradients","Breathing animations, fluid morphing, generative growth, physics-based movement","Sustainability tech, biotech, advanced health, meditation, generative art platforms","Standard SaaS, data grids, strict corporate, accounting",✓ Full,✓ Full,⚠ Moderate,✓ Good,✓ Good,✓ High,"Canvas 10/10, WebGL 10/10",2024+ Generative,High,"Design a biomimetic organic interface. Use: cellular/fluid shapes, breathing animations, generative patterns, bioluminescent colors, physics-based movement, nature algorithms, life-like elements, flowing gradients.","SVG morphing (SMIL or GSAP), canvas for generative, animation: breathing (scale pulse), filter: blur for organic, clip-path for cellular, WebGL for advanced, physics libraries","☐ Organic shapes present, â˜ Animations feel alive, â˜ Generative elements, â˜ Performance monitored, â˜ Mobile fallback, â˜ Accessibility alt content","--cellular-pink: #FF9999, --chlorophyll: #00FF41, --bioluminescent: #00FFFF, --breathing-duration: 4s, --morph-ease: cubic-bezier(0.4, 0, 0.2, 1), --organic-blur: 20px"
59,Anti-Polish / Raw Aesthetic,General,"Hand-drawn, collage, scanned textures, unfinished, imperfect, authentic, human, sketch, raw marks, creative process","Paper White #FAFAF8, Pencil Grey #4A4A4A, Marker Black #1A1A1A, Kraft Brown #C4A77D","Watercolor washes, pencil shading, ink splatters, tape textures, aged paper tones","No smooth transitions, hand-drawn animations, paper texture overlays, jitter effects, sketch reveal","Creative portfolios, artist sites, indie brands, handmade products, authentic storytelling, editorial","Corporate enterprise, fintech, healthcare, government, polished SaaS",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"CSS 10/10, SVG 10/10",2025+ Anti-Digital,Low,"Design with anti-polish raw aesthetic. Use: hand-drawn elements, scanned textures, unfinished look, paper/pencil textures, collage style, authentic imperfection, sketch marks, tape/sticker overlays, human touch.","background: url(paper-texture.png), filter: grayscale() contrast(), border: hand-drawn SVG, transform: rotate(small random), no smooth transitions, sketch-style fonts, opacity variations","☐ Textures loaded, â˜ Hand-drawn elements present, â˜ Imperfections intentional, â˜ Authentic feel achieved, â˜ Performance ok with textures, â˜ Accessibility maintained","--paper-bg: #FAFAF8, --pencil-color: #4A4A4A, --marker-black: #1A1A1A, --kraft-brown: #C4A77D, --sketch-rotation: random(-3deg, 3deg), --texture-opacity: 0.3"
60,Tactile Digital / Deformable UI,General,"Jelly buttons, chrome, clay, squishy, deformable, bouncy, physical, tactile feedback, press response","Gradient metallics, Chrome Silver #C0C0C0, Jelly Pink #FF9ECD, Soft Blue #87CEEB","Glossy highlights, shadow depth, reflection effects, material-specific colors","Press deformation (scale + squish), bounce-back (cubic-bezier), material response, haptic-like feedback, spring physics","Modern mobile apps, playful brands, entertainment, gaming UI, consumer products, interactive demos","Enterprise software, data dashboards, accessibility-critical, professional tools",✓ Full,✓ Full,⚠ Good,⚠ Motion sensitive,✓ High,✓ Very High,"Framer Motion 10/10, React Spring 10/10, GSAP 10/10",2025+ Tactile Era,Medium,"Design a tactile deformable interface. Use: jelly/squishy buttons, press deformation effect, bounce-back animations, chrome/clay materials, spring physics, haptic-like feedback, material response, 3D depth on interaction.","transform: scale(0.95) on active, animation: bounce (cubic-bezier(0.34, 1.56, 0.64, 1)), box-shadow: inset for press, filter: brightness on press, spring physics (react-spring/framer-motion)","☐ Press effect visible, â˜ Bounce-back smooth, â˜ Material feels tactile, â˜ Spring physics tuned, â˜ Mobile touch responsive, â˜ Reduced motion option","--press-scale: 0.95, --bounce-duration: 400ms, --spring-stiffness: 300, --spring-damping: 20, --material-glossy: linear-gradient(135deg, white 0%, transparent 60%), --depth-shadow: 0 10px 30px rgba(0,0,0,0.2)"
61,Nature Distilled,General,"Muted earthy, skin tones, wood, soil, sand, terracotta, warmth, organic materials, handmade warmth","Terracotta #C67B5C, Sand Beige #D4C4A8, Warm Clay #B5651D, Soft Cream #F5F0E1","Earth Brown #8B4513, Olive Green #6B7B3C, Warm Stone #9C8B7A, muted gradients","Subtle parallax, natural easing (ease-out), texture overlays, grain effects, soft shadows","Wellness brands, sustainable products, artisan goods, organic food, spa/beauty, home decor","Tech startups, gaming, nightlife, corporate finance, high-energy brands",✓ Full,◐ Partial,⚡ Excellent,✓ WCAG AA,✓ High,✓ High,"Tailwind 10/10, CSS 10/10",2025+ Handmade Warmth,Low,"Design with nature distilled aesthetic. Use: muted earthy colors (terracotta, sand, olive), organic materials feel, warm tones, handmade warmth, natural textures, artisan quality, sustainable vibe, soft gradients.","background: warm earth tones, color: #C67B5C #D4C4A8 #6B7B3C, border-radius: organic (varied), box-shadow: soft natural, texture overlays (grain), font: humanist sans-serif","☐ Earth tones dominant, â˜ Warm feel achieved, â˜ Textures subtle, â˜ Handmade quality, â˜ Sustainable messaging, â˜ Calming aesthetic","--terracotta: #C67B5C, --sand-beige: #D4C4A8, --warm-clay: #B5651D, --soft-cream: #F5F0E1, --olive-green: #6B7B3C, --grain-opacity: 0.1"
62,Interactive Cursor Design,General,"Custom cursor, cursor as tool, hover effects, cursor feedback, pointer transformation, cursor trail, magnetic cursor","Brand-dependent, cursor accent color, high contrast for visibility","Trail colors, hover state colors, magnetic zone indicators, feedback colors","Cursor scale on hover, magnetic pull to elements, cursor morphing, trail effects, blend mode cursors, click feedback","Creative portfolios, interactive experiences, agency sites, product showcases, gaming, entertainment","Mobile-first (no cursor), accessibility-critical, data-heavy dashboards, forms",✓ Full,✓ Full,⚡ Good,⚠ Not for touch/SR,✗ No cursor,✓ High,"GSAP 10/10, Framer Motion 10/10, Custom JS 10/10",2025+ Interactive,Medium,"Design with interactive cursor effects. Use: custom cursor, cursor morphing on hover, magnetic cursor pull, cursor trails, blend mode cursors, click feedback animations, cursor as interaction tool, pointer transformation.","cursor: none (custom), position: fixed for cursor element, mix-blend-mode: difference, transform on hover targets, magnetic effect (JS position lerp), trail with opacity fade, scale on click","☐ Custom cursor works, â˜ Hover morph smooth, â˜ Magnetic pull subtle, â˜ Trail performance ok, â˜ Click feedback visible, â˜ Touch fallback provided","--cursor-size: 20px, --cursor-hover-scale: 1.5, --magnetic-distance: 100px, --trail-length: 10, --trail-fade: 0.1, --blend-mode: difference"
63,Voice-First Multimodal,General,"Voice UI, multimodal, audio feedback, conversational, hands-free, ambient, contextual, speech recognition","Calm neutrals: Soft White #FAFAFA, Muted Blue #6B8FAF, Gentle Purple #9B8FBB","Audio waveform colors, status indicators (listening/processing/speaking), success/error tones","Voice waveform visualization, listening pulse, processing spinner, speak animation, smooth transitions","Voice assistants, accessibility apps, hands-free tools, smart home, automotive UI, cooking apps","Visual-heavy content, data entry, complex forms, noisy environments",✓ Full,✓ Full,⚡ Excellent,✓ Excellent,✓ High,✓ High,"Web Speech API 10/10, React 10/10",2025+ Voice Era,Medium,"Design a voice-first multimodal interface. Use: voice waveform visualization, listening state indicator, speaking animation, minimal visible UI, audio feedback cues, hands-free optimized, conversational flow, ambient design.","Web Speech API integration, canvas for waveform, animation: pulse for listening, status indicators (color change), audio visualization (Web Audio API), minimal chrome, large touch targets","☐ Voice recognition works, â˜ Visual feedback clear, â˜ Listening state obvious, â˜ Speaking animation smooth, â˜ Fallback UI provided, â˜ Accessibility excellent","--listening-color: #6B8FAF, --speaking-color: #22C55E, --waveform-height: 60px, --pulse-duration: 1.5s, --indicator-size: 24px, --voice-accent: #9B8FBB"
64,3D Product Preview,General,"360 product view, rotatable, zoomable, touch-to-spin, AR preview, product configurator, interactive 3D model","Product-dependent, neutral backgrounds: Soft Grey #E8E8E8, Pure White #FFFFFF","Shadow gradients, reflection planes, environment lighting colors, accent highlights","Drag-to-rotate, pinch-to-zoom, spin animation, AR placement, material switching, smooth orbit controls","E-commerce, furniture, fashion, automotive, electronics, jewelry, product configurators","Content-heavy sites, blogs, dashboards, low-bandwidth, accessibility-critical",◐ Partial,◐ Partial,❌ Poor (3D rendering),⚠ Alt content needed,◐ Medium,✓ Very High,"Three.js 10/10, model-viewer 10/10, Spline 9/10",2025+ E-commerce 3D,High,"Design a 3D product preview interface. Use: 360° rotation, drag-to-spin, pinch-to-zoom, AR preview button, material/color switcher, hotspot annotations, orbit controls, product configurator, smooth rendering.","Three.js or model-viewer, OrbitControls, touch events for rotation, WebXR for AR, canvas with WebGL, loading placeholder, LOD for performance, environment lighting","☐ 3D model loads fast, â˜ Rotation smooth, â˜ Zoom works (pinch/scroll), â˜ AR button functional, â˜ Colors switchable, â˜ Mobile touch works","--canvas-bg: #F5F5F5, --hotspot-color: #3B82F6, --loading-spinner: primary, --rotation-speed: 0.5, --zoom-min: 0.5, --zoom-max: 2"
65,Gradient Mesh / Aurora Evolved,General,"Complex gradients, mesh gradients, multi-color blend, aurora effect, flowing colors, iridescent, holographic, prismatic","Multi-stop gradients: Cyan #00FFFF, Magenta #FF00FF, Yellow #FFFF00, Blue #0066FF, Green #00FF66","Complementary mesh points, smooth color transitions, iridescent overlays, chromatic shifts","CSS mesh-gradient (experimental), SVG gradients, canvas gradients, smooth color morphing, flowing animation","Hero sections, backgrounds, creative brands, music platforms, fashion, lifestyle, premium products","Data interfaces, text-heavy content, accessibility-critical, conservative brands",✓ Full,✓ Full,⚠ Good,⚠ Text contrast,✓ Good,✓ High,"CSS 8/10, SVG 10/10, Canvas 10/10",2025+ Gradient Evolution,Medium,"Design with gradient mesh aurora effect. Use: multi-color mesh gradients, flowing color transitions, aurora/northern lights feel, iridescent overlays, holographic shimmer, prismatic effects, smooth color morphing.","background: conic-gradient or mesh (SVG), animation: gradient flow (background-position), filter: hue-rotate for shimmer, mix-blend-mode: screen, canvas for complex mesh, multiple gradient layers","☐ Mesh gradient visible, â˜ Colors flow smoothly, â˜ Aurora effect achieved, â˜ Performance acceptable, â˜ Text remains readable, â˜ Mobile renders ok","--mesh-color-1: #00FFFF, --mesh-color-2: #FF00FF, --mesh-color-3: #FFFF00, --mesh-color-4: #00FF66, --flow-duration: 10s, --shimmer-intensity: 0.3"
66,Editorial Grid / Magazine,General,"Magazine layout, asymmetric grid, editorial typography, pull quotes, drop caps, column layout, print-inspired","High contrast: Black #000000, White #FFFFFF, accent brand color","Muted supporting, pull quote highlights, byline colors, section dividers","Smooth scroll, reveal on scroll, parallax images, text animations, page-flip transitions","News sites, blogs, magazines, editorial content, long-form articles, journalism, publishing","Dashboards, apps, e-commerce catalogs, real-time data, short-form content",✓ Full,✓ Full,⚡ Excellent,✓ WCAG AAA,✓ High,✓ Medium,"CSS Grid 10/10, Tailwind 10/10",2020s Editorial Digital,Low,"Design an editorial magazine layout. Use: asymmetric grid, pull quotes, drop caps, multi-column text, large imagery, bylines, section dividers, print-inspired typography, article hierarchy, white space balance.","display: grid with named areas, column-count for text, ::first-letter for drop caps, blockquote styling, figure/figcaption, gap variations, font: serif for body, variable widths","☐ Grid asymmetric, â˜ Typography editorial, â˜ Pull quotes styled, â˜ Drop caps present, â˜ Images large/impactful, â˜ Mobile reflows well","--grid-cols: asymmetric, --body-font: Georgia/Merriweather, --heading-font: bold sans, --drop-cap-size: 4em, --pull-quote-size: 1.5em, --column-gap: 2rem"
67,Chromatic Aberration / RGB Split,General,"RGB split, color fringing, glitch, retro tech, VHS, analog error, distortion, lens effect","Offset RGB: Red #FF0000, Green #00FF00, Blue #0000FF, Black #000000","Neon accents, scan lines, noise overlays, error colors","RGB offset animation, glitch timing, scan line movement, noise flicker, distortion on hover","Music platforms, gaming, tech brands, creative portfolios, nightlife, entertainment, video platforms","Corporate, healthcare, finance, accessibility-critical, elderly users",✓ Full,✓ Dark preferred,⚠ Good,⚠ Can cause strain,◐ Medium,✓ High,"CSS filters 10/10, GSAP 10/10",2020s Retro-Tech,Medium,"Design with chromatic aberration RGB split effect. Use: color channel offset (R/G/B), glitch aesthetic, retro tech feel, VHS error look, lens distortion, scan lines, noise overlay, analog imperfection.","filter: drop-shadow with offset colors, text-shadow: RGB offset (-2px 0 red, 2px 0 cyan), animation: glitch (random offset), ::before for scanlines, mix-blend-mode: screen for overlays","☐ RGB split visible, â˜ Glitch effect controlled, â˜ Scan lines subtle, â˜ Performance ok, â˜ Readability maintained, â˜ Reduced motion option","--rgb-offset: 2px, --red-channel: #FF0000, --green-channel: #00FF00, --blue-channel: #0000FF, --glitch-duration: 0.3s, --scanline-opacity: 0.1"
68,Vintage Analog / Retro Film,General,"Film grain, VHS, cassette tape, polaroid, analog warmth, faded colors, light leaks, vintage photography","Faded Cream #F5E6C8, Warm Sepia #D4A574, Muted Teal #4A7B7C, Soft Pink #E8B4B8","Grain overlays, light leak oranges, shadow blues, vintage paper tones, desaturated accents","Film grain overlay, VHS tracking effect, polaroid shake, fade-in transitions, light leak animations","Photography portfolios, music/vinyl brands, vintage fashion, nostalgia marketing, film industry, cafes","Modern tech, SaaS, healthcare, children's apps, corporate enterprise",✓ Full,◐ Partial,⚡ Good,✓ WCAG AA,✓ High,✓ High,"CSS filters 10/10, Canvas 9/10",1970s-90s Analog Revival,Medium,"Design with vintage analog film aesthetic. Use: film grain overlay, faded/desaturated colors, warm sepia tones, light leaks, VHS tracking effect, polaroid frame, analog warmth, nostalgic photography feel.","filter: sepia() contrast() saturate(0.8), background: noise texture overlay, animation: VHS tracking (transform skew), light leak gradient overlay, border for polaroid frame, grain via SVG filter","☐ Film grain visible, â˜ Colors faded/warm, â˜ Light leaks present, â˜ Nostalgic feel achieved, â˜ Performance with filters, â˜ Images look vintage","--sepia-amount: 20%, --contrast: 1.1, --saturation: 0.8, --grain-opacity: 0.15, --light-leak-color: rgba(255,200,100,0.2), --warm-tint: #F5E6C8"
.claude/skills/ui-ux-pro-max/data/typography.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
No,Font Pairing Name,Category,Heading Font,Body Font,Mood/Style Keywords,Best For,Google Fonts URL,CSS Import,Tailwind Config,Notes
1,Classic Elegant,"Serif + Sans",Playfair Display,Inter,"elegant, luxury, sophisticated, timeless, premium, editorial","Luxury brands, fashion, spa, beauty, editorial, magazines, high-end e-commerce","https://fonts.google.com/share?selection.family=Inter:wght@300;400;500;600;700|Playfair+Display:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@400;500;600;700&display=swap');","fontFamily: { serif: ['Playfair Display', 'serif'], sans: ['Inter', 'sans-serif'] }","High contrast between elegant heading and clean body. Perfect for luxury/premium."
2,Modern Professional,"Sans + Sans",Poppins,Open Sans,"modern, professional, clean, corporate, friendly, approachable","SaaS, corporate sites, business apps, startups, professional services","https://fonts.google.com/share?selection.family=Open+Sans:wght@300;400;500;600;700|Poppins:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&family=Poppins:wght@400;500;600;700&display=swap');","fontFamily: { heading: ['Poppins', 'sans-serif'], body: ['Open Sans', 'sans-serif'] }","Geometric Poppins for headings, humanist Open Sans for readability."
3,Tech Startup,"Sans + Sans",Space Grotesk,DM Sans,"tech, startup, modern, innovative, bold, futuristic","Tech companies, startups, SaaS, developer tools, AI products","https://fonts.google.com/share?selection.family=DM+Sans:wght@400;500;700|Space+Grotesk:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');","fontFamily: { heading: ['Space Grotesk', 'sans-serif'], body: ['DM Sans', 'sans-serif'] }","Space Grotesk has unique character, DM Sans is highly readable."
4,Editorial Classic,"Serif + Serif",Cormorant Garamond,Libre Baskerville,"editorial, classic, literary, traditional, refined, bookish","Publishing, blogs, news sites, literary magazines, book covers","https://fonts.google.com/share?selection.family=Cormorant+Garamond:wght@400;500;600;700|Libre+Baskerville:wght@400;700","@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&family=Libre+Baskerville:wght@400;700&display=swap');","fontFamily: { heading: ['Cormorant Garamond', 'serif'], body: ['Libre Baskerville', 'serif'] }","All-serif pairing for traditional editorial feel."
5,Minimal Swiss,"Sans + Sans",Inter,Inter,"minimal, clean, swiss, functional, neutral, professional","Dashboards, admin panels, documentation, enterprise apps, design systems","https://fonts.google.com/share?selection.family=Inter:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');","fontFamily: { sans: ['Inter', 'sans-serif'] }","Single font family with weight variations. Ultimate simplicity."
6,Playful Creative,"Display + Sans",Fredoka,Nunito,"playful, friendly, fun, creative, warm, approachable","Children's apps, educational, gaming, creative tools, entertainment","https://fonts.google.com/share?selection.family=Fredoka:wght@400;500;600;700|Nunito:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Fredoka', 'sans-serif'], body: ['Nunito', 'sans-serif'] }","Rounded, friendly fonts perfect for playful UIs."
7,Bold Statement,"Display + Sans",Bebas Neue,Source Sans 3,"bold, impactful, strong, dramatic, modern, headlines","Marketing sites, portfolios, agencies, event pages, sports","https://fonts.google.com/share?selection.family=Bebas+Neue|Source+Sans+3:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Source+Sans+3:wght@300;400;500;600;700&display=swap');","fontFamily: { display: ['Bebas Neue', 'sans-serif'], body: ['Source Sans 3', 'sans-serif'] }","Bebas Neue for large headlines only. All-caps display font."
8,Wellness Calm,"Serif + Sans",Lora,Raleway,"calm, wellness, health, relaxing, natural, organic","Health apps, wellness, spa, meditation, yoga, organic brands","https://fonts.google.com/share?selection.family=Lora:wght@400;500;600;700|Raleway:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&family=Raleway:wght@300;400;500;600;700&display=swap');","fontFamily: { serif: ['Lora', 'serif'], sans: ['Raleway', 'sans-serif'] }","Lora's organic curves with Raleway's elegant simplicity."
9,Developer Mono,"Mono + Sans",JetBrains Mono,IBM Plex Sans,"code, developer, technical, precise, functional, hacker","Developer tools, documentation, code editors, tech blogs, CLI apps","https://fonts.google.com/share?selection.family=IBM+Plex+Sans:wght@300;400;500;600;700|JetBrains+Mono:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');","fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['IBM Plex Sans', 'sans-serif'] }","JetBrains for code, IBM Plex for UI. Developer-focused."
10,Retro Vintage,"Display + Serif",Abril Fatface,Merriweather,"retro, vintage, nostalgic, dramatic, decorative, bold","Vintage brands, breweries, restaurants, creative portfolios, posters","https://fonts.google.com/share?selection.family=Abril+Fatface|Merriweather:wght@300;400;700","@import url('https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Merriweather:wght@300;400;700&display=swap');","fontFamily: { display: ['Abril Fatface', 'serif'], body: ['Merriweather', 'serif'] }","Abril Fatface for hero headlines only. High-impact vintage feel."
11,Geometric Modern,"Sans + Sans",Outfit,Work Sans,"geometric, modern, clean, balanced, contemporary, versatile","General purpose, portfolios, agencies, modern brands, landing pages","https://fonts.google.com/share?selection.family=Outfit:wght@300;400;500;600;700|Work+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Work+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Outfit', 'sans-serif'], body: ['Work Sans', 'sans-serif'] }","Both geometric but Outfit more distinctive for headings."
12,Luxury Serif,"Serif + Sans",Cormorant,Montserrat,"luxury, high-end, fashion, elegant, refined, premium","Fashion brands, luxury e-commerce, jewelry, high-end services","https://fonts.google.com/share?selection.family=Cormorant:wght@400;500;600;700|Montserrat:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Cormorant:wght@400;500;600;700&family=Montserrat:wght@300;400;500;600;700&display=swap');","fontFamily: { serif: ['Cormorant', 'serif'], sans: ['Montserrat', 'sans-serif'] }","Cormorant's elegance with Montserrat's geometric precision."
13,Friendly SaaS,"Sans + Sans",Plus Jakarta Sans,Plus Jakarta Sans,"friendly, modern, saas, clean, approachable, professional","SaaS products, web apps, dashboards, B2B, productivity tools","https://fonts.google.com/share?selection.family=Plus+Jakarta+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { sans: ['Plus Jakarta Sans', 'sans-serif'] }","Single versatile font. Modern alternative to Inter."
14,News Editorial,"Serif + Sans",Newsreader,Roboto,"news, editorial, journalism, trustworthy, readable, informative","News sites, blogs, magazines, journalism, content-heavy sites","https://fonts.google.com/share?selection.family=Newsreader:wght@400;500;600;700|Roboto:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Newsreader:wght@400;500;600;700&family=Roboto:wght@300;400;500;700&display=swap');","fontFamily: { serif: ['Newsreader', 'serif'], sans: ['Roboto', 'sans-serif'] }","Newsreader designed for long-form reading. Roboto for UI."
15,Handwritten Charm,"Script + Sans",Caveat,Quicksand,"handwritten, personal, friendly, casual, warm, charming","Personal blogs, invitations, creative portfolios, lifestyle brands","https://fonts.google.com/share?selection.family=Caveat:wght@400;500;600;700|Quicksand:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&family=Quicksand:wght@300;400;500;600;700&display=swap');","fontFamily: { script: ['Caveat', 'cursive'], sans: ['Quicksand', 'sans-serif'] }","Use Caveat sparingly for accents. Quicksand for body."
16,Corporate Trust,"Sans + Sans",Lexend,Source Sans 3,"corporate, trustworthy, accessible, readable, professional, clean","Enterprise, government, healthcare, finance, accessibility-focused","https://fonts.google.com/share?selection.family=Lexend:wght@300;400;500;600;700|Source+Sans+3:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&family=Source+Sans+3:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Lexend', 'sans-serif'], body: ['Source Sans 3', 'sans-serif'] }","Lexend designed for readability. Excellent accessibility."
17,Brutalist Raw,"Mono + Mono",Space Mono,Space Mono,"brutalist, raw, technical, monospace, minimal, stark","Brutalist designs, developer portfolios, experimental, tech art","https://fonts.google.com/share?selection.family=Space+Mono:wght@400;700","@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');","fontFamily: { mono: ['Space Mono', 'monospace'] }","All-mono for raw brutalist aesthetic. Limited weights."
18,Fashion Forward,"Sans + Sans",Syne,Manrope,"fashion, avant-garde, creative, bold, artistic, edgy","Fashion brands, creative agencies, art galleries, design studios","https://fonts.google.com/share?selection.family=Manrope:wght@300;400;500;600;700|Syne:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600;700&family=Syne:wght@400;500;600;700&display=swap');","fontFamily: { heading: ['Syne', 'sans-serif'], body: ['Manrope', 'sans-serif'] }","Syne's unique character for headlines. Manrope for readability."
19,Soft Rounded,"Sans + Sans",Varela Round,Nunito Sans,"soft, rounded, friendly, approachable, warm, gentle","Children's products, pet apps, friendly brands, wellness, soft UI","https://fonts.google.com/share?selection.family=Nunito+Sans:wght@300;400;500;600;700|Varela+Round","@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;500;600;700&family=Varela+Round&display=swap');","fontFamily: { heading: ['Varela Round', 'sans-serif'], body: ['Nunito Sans', 'sans-serif'] }","Both rounded and friendly. Perfect for soft UI designs."
20,Premium Sans,"Sans + Sans",Satoshi,General Sans,"premium, modern, clean, sophisticated, versatile, balanced","Premium brands, modern agencies, SaaS, portfolios, startups","https://fonts.google.com/share?selection.family=DM+Sans:wght@400;500;700","@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap');","fontFamily: { sans: ['DM Sans', 'sans-serif'] }","Note: Satoshi/General Sans on Fontshare. DM Sans as Google alternative."
21,Vietnamese Friendly,"Sans + Sans",Be Vietnam Pro,Noto Sans,"vietnamese, international, readable, clean, multilingual, accessible","Vietnamese sites, multilingual apps, international products","https://fonts.google.com/share?selection.family=Be+Vietnam+Pro:wght@300;400;500;600;700|Noto+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;500;600;700&family=Noto+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { sans: ['Be Vietnam Pro', 'Noto Sans', 'sans-serif'] }","Be Vietnam Pro excellent Vietnamese support. Noto as fallback."
22,Japanese Elegant,"Serif + Sans",Noto Serif JP,Noto Sans JP,"japanese, elegant, traditional, modern, multilingual, readable","Japanese sites, Japanese restaurants, cultural sites, anime/manga","https://fonts.google.com/share?selection.family=Noto+Sans+JP:wght@300;400;500;700|Noto+Serif+JP:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700&family=Noto+Serif+JP:wght@400;500;600;700&display=swap');","fontFamily: { serif: ['Noto Serif JP', 'serif'], sans: ['Noto Sans JP', 'sans-serif'] }","Noto fonts excellent Japanese support. Traditional + modern feel."
23,Korean Modern,"Sans + Sans",Noto Sans KR,Noto Sans KR,"korean, modern, clean, professional, multilingual, readable","Korean sites, K-beauty, K-pop, Korean businesses, multilingual","https://fonts.google.com/share?selection.family=Noto+Sans+KR:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');","fontFamily: { sans: ['Noto Sans KR', 'sans-serif'] }","Clean Korean typography. Single font with weight variations."
24,Chinese Traditional,"Serif + Sans",Noto Serif TC,Noto Sans TC,"chinese, traditional, elegant, cultural, multilingual, readable","Traditional Chinese sites, cultural content, Taiwan/Hong Kong markets","https://fonts.google.com/share?selection.family=Noto+Sans+TC:wght@300;400;500;700|Noto+Serif+TC:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700&family=Noto+Serif+TC:wght@400;500;600;700&display=swap');","fontFamily: { serif: ['Noto Serif TC', 'serif'], sans: ['Noto Sans TC', 'sans-serif'] }","Traditional Chinese character support. Elegant pairing."
25,Chinese Simplified,"Sans + Sans",Noto Sans SC,Noto Sans SC,"chinese, simplified, modern, professional, multilingual, readable","Simplified Chinese sites, mainland China market, business apps","https://fonts.google.com/share?selection.family=Noto+Sans+SC:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');","fontFamily: { sans: ['Noto Sans SC', 'sans-serif'] }","Simplified Chinese support. Clean modern look."
26,Arabic Elegant,"Serif + Sans",Noto Naskh Arabic,Noto Sans Arabic,"arabic, elegant, traditional, cultural, RTL, readable","Arabic sites, Middle East market, Islamic content, bilingual sites","https://fonts.google.com/share?selection.family=Noto+Naskh+Arabic:wght@400;500;600;700|Noto+Sans+Arabic:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@300;400;500;700&display=swap');","fontFamily: { serif: ['Noto Naskh Arabic', 'serif'], sans: ['Noto Sans Arabic', 'sans-serif'] }","RTL support. Naskh for traditional, Sans for modern Arabic."
27,Thai Modern,"Sans + Sans",Noto Sans Thai,Noto Sans Thai,"thai, modern, readable, clean, multilingual, accessible","Thai sites, Southeast Asia, tourism, Thai restaurants","https://fonts.google.com/share?selection.family=Noto+Sans+Thai:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@300;400;500;700&display=swap');","fontFamily: { sans: ['Noto Sans Thai', 'sans-serif'] }","Clean Thai typography. Excellent readability."
28,Hebrew Modern,"Sans + Sans",Noto Sans Hebrew,Noto Sans Hebrew,"hebrew, modern, RTL, clean, professional, readable","Hebrew sites, Israeli market, Jewish content, bilingual sites","https://fonts.google.com/share?selection.family=Noto+Sans+Hebrew:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@300;400;500;700&display=swap');","fontFamily: { sans: ['Noto Sans Hebrew', 'sans-serif'] }","RTL support. Clean modern Hebrew typography."
29,Legal Professional,"Serif + Sans",EB Garamond,Lato,"legal, professional, traditional, trustworthy, formal, authoritative","Law firms, legal services, contracts, formal documents, government","https://fonts.google.com/share?selection.family=EB+Garamond:wght@400;500;600;700|Lato:wght@300;400;700","@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:wght@400;500;600;700&family=Lato:wght@300;400;700&display=swap');","fontFamily: { serif: ['EB Garamond', 'serif'], sans: ['Lato', 'sans-serif'] }","EB Garamond for authority. Lato for clean body text."
30,Medical Clean,"Sans + Sans",Figtree,Noto Sans,"medical, clean, accessible, professional, healthcare, trustworthy","Healthcare, medical clinics, pharma, health apps, accessibility","https://fonts.google.com/share?selection.family=Figtree:wght@300;400;500;600;700|Noto+Sans:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Figtree:wght@300;400;500;600;700&family=Noto+Sans:wght@300;400;500;700&display=swap');","fontFamily: { heading: ['Figtree', 'sans-serif'], body: ['Noto Sans', 'sans-serif'] }","Clean, accessible fonts for medical contexts."
31,Financial Trust,"Sans + Sans",IBM Plex Sans,IBM Plex Sans,"financial, trustworthy, professional, corporate, banking, serious","Banks, finance, insurance, investment, fintech, enterprise","https://fonts.google.com/share?selection.family=IBM+Plex+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { sans: ['IBM Plex Sans', 'sans-serif'] }","IBM Plex conveys trust and professionalism. Excellent for data."
32,Real Estate Luxury,"Serif + Sans",Cinzel,Josefin Sans,"real estate, luxury, elegant, sophisticated, property, premium","Real estate, luxury properties, architecture, interior design","https://fonts.google.com/share?selection.family=Cinzel:wght@400;500;600;700|Josefin+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Josefin+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { serif: ['Cinzel', 'serif'], sans: ['Josefin Sans', 'sans-serif'] }","Cinzel's elegance for headlines. Josefin for modern body."
33,Restaurant Menu,"Serif + Sans",Playfair Display SC,Karla,"restaurant, menu, culinary, elegant, foodie, hospitality","Restaurants, cafes, food blogs, culinary, hospitality","https://fonts.google.com/share?selection.family=Karla:wght@300;400;500;600;700|Playfair+Display+SC:wght@400;700","@import url('https://fonts.googleapis.com/css2?family=Karla:wght@300;400;500;600;700&family=Playfair+Display+SC:wght@400;700&display=swap');","fontFamily: { display: ['Playfair Display SC', 'serif'], sans: ['Karla', 'sans-serif'] }","Small caps Playfair for menu headers. Karla for descriptions."
34,Art Deco,"Display + Sans",Poiret One,Didact Gothic,"art deco, vintage, 1920s, elegant, decorative, gatsby","Vintage events, art deco themes, luxury hotels, classic cocktails","https://fonts.google.com/share?selection.family=Didact+Gothic|Poiret+One","@import url('https://fonts.googleapis.com/css2?family=Didact+Gothic&family=Poiret+One&display=swap');","fontFamily: { display: ['Poiret One', 'sans-serif'], sans: ['Didact Gothic', 'sans-serif'] }","Poiret One for art deco headlines only. Didact for body."
35,Magazine Style,"Serif + Sans",Libre Bodoni,Public Sans,"magazine, editorial, publishing, refined, journalism, print","Magazines, online publications, editorial content, journalism","https://fonts.google.com/share?selection.family=Libre+Bodoni:wght@400;500;600;700|Public+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Libre+Bodoni:wght@400;500;600;700&family=Public+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { serif: ['Libre Bodoni', 'serif'], sans: ['Public Sans', 'sans-serif'] }","Bodoni's editorial elegance. Public Sans for clean UI."
36,Crypto/Web3,"Sans + Sans",Orbitron,Exo 2,"crypto, web3, futuristic, tech, blockchain, digital","Crypto platforms, NFT, blockchain, web3, futuristic tech","https://fonts.google.com/share?selection.family=Exo+2:wght@300;400;500;600;700|Orbitron:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700&display=swap');","fontFamily: { display: ['Orbitron', 'sans-serif'], body: ['Exo 2', 'sans-serif'] }","Orbitron for futuristic headers. Exo 2 for readable body."
37,Gaming Bold,"Display + Sans",Russo One,Chakra Petch,"gaming, bold, action, esports, competitive, energetic","Gaming, esports, action games, competitive sports, entertainment","https://fonts.google.com/share?selection.family=Chakra+Petch:wght@300;400;500;600;700|Russo+One","@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@300;400;500;600;700&family=Russo+One&display=swap');","fontFamily: { display: ['Russo One', 'sans-serif'], body: ['Chakra Petch', 'sans-serif'] }","Russo One for impact. Chakra Petch for techy body text."
38,Indie/Craft,"Display + Sans",Amatic SC,Cabin,"indie, craft, handmade, artisan, organic, creative","Craft brands, indie products, artisan, handmade, organic products","https://fonts.google.com/share?selection.family=Amatic+SC:wght@400;700|Cabin:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Amatic+SC:wght@400;700&family=Cabin:wght@400;500;600;700&display=swap');","fontFamily: { display: ['Amatic SC', 'sans-serif'], sans: ['Cabin', 'sans-serif'] }","Amatic for handwritten feel. Cabin for readable body."
39,Startup Bold,"Sans + Sans",Clash Display,Satoshi,"startup, bold, modern, innovative, confident, dynamic","Startups, pitch decks, product launches, bold brands","https://fonts.google.com/share?selection.family=Outfit:wght@400;500;600;700|Rubik:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Rubik:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Outfit', 'sans-serif'], body: ['Rubik', 'sans-serif'] }","Note: Clash Display on Fontshare. Outfit as Google alternative."
40,E-commerce Clean,"Sans + Sans",Rubik,Nunito Sans,"ecommerce, clean, shopping, product, retail, conversion","E-commerce, online stores, product pages, retail, shopping","https://fonts.google.com/share?selection.family=Nunito+Sans:wght@300;400;500;600;700|Rubik:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;500;600;700&family=Rubik:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Rubik', 'sans-serif'], body: ['Nunito Sans', 'sans-serif'] }","Clean readable fonts perfect for product descriptions."
41,Academic/Research,"Serif + Sans",Crimson Pro,Atkinson Hyperlegible,"academic, research, scholarly, accessible, readable, educational","Universities, research papers, academic journals, educational","https://fonts.google.com/share?selection.family=Atkinson+Hyperlegible:wght@400;700|Crimson+Pro:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&family=Crimson+Pro:wght@400;500;600;700&display=swap');","fontFamily: { serif: ['Crimson Pro', 'serif'], sans: ['Atkinson Hyperlegible', 'sans-serif'] }","Crimson for scholarly headlines. Atkinson for accessibility."
42,Dashboard Data,"Mono + Sans",Fira Code,Fira Sans,"dashboard, data, analytics, code, technical, precise","Dashboards, analytics, data visualization, admin panels","https://fonts.google.com/share?selection.family=Fira+Code:wght@400;500;600;700|Fira+Sans:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Fira+Sans:wght@300;400;500;600;700&display=swap');","fontFamily: { mono: ['Fira Code', 'monospace'], sans: ['Fira Sans', 'sans-serif'] }","Fira family cohesion. Code for data, Sans for labels."
43,Music/Entertainment,"Display + Sans",Righteous,Poppins,"music, entertainment, fun, energetic, bold, performance","Music platforms, entertainment, events, festivals, performers","https://fonts.google.com/share?selection.family=Poppins:wght@300;400;500;600;700|Righteous","@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Righteous&display=swap');","fontFamily: { display: ['Righteous', 'sans-serif'], sans: ['Poppins', 'sans-serif'] }","Righteous for bold entertainment headers. Poppins for body."
44,Minimalist Portfolio,"Sans + Sans",Archivo,Space Grotesk,"minimal, portfolio, designer, creative, clean, artistic","Design portfolios, creative professionals, minimalist brands","https://fonts.google.com/share?selection.family=Archivo:wght@300;400;500;600;700|Space+Grotesk:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');","fontFamily: { heading: ['Space Grotesk', 'sans-serif'], body: ['Archivo', 'sans-serif'] }","Space Grotesk for distinctive headers. Archivo for clean body."
45,Kids/Education,"Display + Sans",Baloo 2,Comic Neue,"kids, education, playful, friendly, colorful, learning","Children's apps, educational games, kid-friendly content","https://fonts.google.com/share?selection.family=Baloo+2:wght@400;500;600;700|Comic+Neue:wght@300;400;700","@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@400;500;600;700&family=Comic+Neue:wght@300;400;700&display=swap');","fontFamily: { display: ['Baloo 2', 'sans-serif'], sans: ['Comic Neue', 'sans-serif'] }","Fun, playful fonts for children. Comic Neue is readable comic style."
46,Wedding/Romance,"Script + Serif",Great Vibes,Cormorant Infant,"wedding, romance, elegant, script, invitation, feminine","Wedding sites, invitations, romantic brands, bridal","https://fonts.google.com/share?selection.family=Cormorant+Infant:wght@300;400;500;600;700|Great+Vibes","@import url('https://fonts.googleapis.com/css2?family=Cormorant+Infant:wght@300;400;500;600;700&family=Great+Vibes&display=swap');","fontFamily: { script: ['Great Vibes', 'cursive'], serif: ['Cormorant Infant', 'serif'] }","Great Vibes for elegant accents. Cormorant for readable text."
47,Science/Tech,"Sans + Sans",Exo,Roboto Mono,"science, technology, research, data, futuristic, precise","Science, research, tech documentation, data-heavy sites","https://fonts.google.com/share?selection.family=Exo:wght@300;400;500;600;700|Roboto+Mono:wght@300;400;500;700","@import url('https://fonts.googleapis.com/css2?family=Exo:wght@300;400;500;600;700&family=Roboto+Mono:wght@300;400;500;700&display=swap');","fontFamily: { sans: ['Exo', 'sans-serif'], mono: ['Roboto Mono', 'monospace'] }","Exo for modern tech feel. Roboto Mono for code/data."
48,Accessibility First,"Sans + Sans",Atkinson Hyperlegible,Atkinson Hyperlegible,"accessible, readable, inclusive, WCAG, dyslexia-friendly, clear","Accessibility-critical sites, government, healthcare, inclusive design","https://fonts.google.com/share?selection.family=Atkinson+Hyperlegible:wght@400;700","@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:wght@400;700&display=swap');","fontFamily: { sans: ['Atkinson Hyperlegible', 'sans-serif'] }","Designed for maximum legibility. Excellent for accessibility."
49,Sports/Fitness,"Sans + Sans",Barlow Condensed,Barlow,"sports, fitness, athletic, energetic, condensed, action","Sports, fitness, gyms, athletic brands, competition","https://fonts.google.com/share?selection.family=Barlow+Condensed:wght@400;500;600;700|Barlow:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600;700&family=Barlow:wght@300;400;500;600;700&display=swap');","fontFamily: { display: ['Barlow Condensed', 'sans-serif'], body: ['Barlow', 'sans-serif'] }","Condensed for impact headlines. Regular Barlow for body."
50,Luxury Minimalist,"Serif + Sans",Bodoni Moda,Jost,"luxury, minimalist, high-end, sophisticated, refined, premium","Luxury minimalist brands, high-end fashion, premium products","https://fonts.google.com/share?selection.family=Bodoni+Moda:wght@400;500;600;700|Jost:wght@300;400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Bodoni+Moda:wght@400;500;600;700&family=Jost:wght@300;400;500;600;700&display=swap');","fontFamily: { serif: ['Bodoni Moda', 'serif'], sans: ['Jost', 'sans-serif'] }","Bodoni's high contrast elegance. Jost for geometric body."
51,Tech/HUD Mono,"Mono + Mono",Share Tech Mono,Fira Code,"tech, futuristic, hud, sci-fi, data, monospaced, precise","Sci-fi interfaces, developer tools, cybersecurity, dashboards","https://fonts.google.com/share?selection.family=Fira+Code:wght@300;400;500;600;700|Share+Tech+Mono","@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Share+Tech+Mono&display=swap');","fontFamily: { hud: ['Share Tech Mono', 'monospace'], code: ['Fira Code', 'monospace'] }","Share Tech Mono has that classic sci-fi look."
52,Pixel Retro,"Display + Sans",Press Start 2P,VT323,"pixel, retro, gaming, 8-bit, nostalgic, arcade","Pixel art games, retro websites, creative portfolios","https://fonts.google.com/share?selection.family=Press+Start+2P|VT323","@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap');","fontFamily: { pixel: ['Press Start 2P', 'cursive'], terminal: ['VT323', 'monospace'] }","Press Start 2P is very wide/large. VT323 is better for body text."
53,Neubrutalist Bold,"Display + Sans",Lexend Mega,Public Sans,"bold, neubrutalist, loud, strong, geometric, quirky","Neubrutalist designs, Gen Z brands, bold marketing","https://fonts.google.com/share?selection.family=Lexend+Mega:wght@100..900|Public+Sans:wght@100..900","@import url('https://fonts.googleapis.com/css2?family=Lexend+Mega:wght@100..900&family=Public+Sans:wght@100..900&display=swap');","fontFamily: { mega: ['Lexend Mega', 'sans-serif'], body: ['Public Sans', 'sans-serif'] }","Lexend Mega has distinct character and variable weight."
54,Academic/Archival,"Serif + Serif",EB Garamond,Crimson Text,"academic, old-school, university, research, serious, traditional","University sites, archives, research papers, history","https://fonts.google.com/share?selection.family=Crimson+Text:wght@400;600;700|EB+Garamond:wght@400;500;600;700;800","@import url('https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600;700&family=EB+Garamond:wght@400;500;600;700;800&display=swap');","fontFamily: { classic: ['EB Garamond', 'serif'], text: ['Crimson Text', 'serif'] }","Classic academic aesthetic. Very legible."
55,Spatial Clear,"Sans + Sans",Inter,Inter,"spatial, legible, glass, system, clean, neutral","Spatial computing, AR/VR, glassmorphism interfaces","https://fonts.google.com/share?selection.family=Inter:wght@300;400;500;600","@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');","fontFamily: { sans: ['Inter', 'sans-serif'] }","Optimized for readability on dynamic backgrounds."
56,Kinetic Motion,"Display + Mono",Syncopate,Space Mono,"kinetic, motion, futuristic, speed, wide, tech","Music festivals, automotive, high-energy brands","https://fonts.google.com/share?selection.family=Space+Mono:wght@400;700|Syncopate:wght@400;700","@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syncopate:wght@400;700&display=swap');","fontFamily: { display: ['Syncopate', 'sans-serif'], mono: ['Space Mono', 'monospace'] }","Syncopate's wide stance works well with motion effects."
57,Gen Z Brutal,"Display + Sans",Anton,Epilogue,"brutal, loud, shouty, meme, internet, bold","Gen Z marketing, streetwear, viral campaigns","https://fonts.google.com/share?selection.family=Anton|Epilogue:wght@400;500;600;700","@import url('https://fonts.googleapis.com/css2?family=Anton&family=Epilogue:wght@400;500;600;700&display=swap');","fontFamily: { display: ['Anton', 'sans-serif'], body: ['Epilogue', 'sans-serif'] }","Anton is impactful and condensed. Good for stickers/badges."
.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
No,UI_Category,Recommended_Pattern,Style_Priority,Color_Mood,Typography_Mood,Key_Effects,Decision_Rules,Anti_Patterns,Severity
1,SaaS (General),Hero + Features + CTA,Glassmorphism + Flat Design,Trust blue + Accent contrast,Professional + Hierarchy,Subtle hover (200-250ms) + Smooth transitions,"{""if_ux_focused"": ""prioritize-minimalism"", ""if_data_heavy"": ""add-glassmorphism""}",Excessive animation + Dark mode by default,HIGH
2,Micro SaaS,Minimal & Direct + Demo,Flat Design + Vibrant & Block,Vibrant primary + White space,Bold + Clean typography,Large CTA hover (300ms) + Scroll reveal,"{""if_quick_onboarding"": ""reduce-steps"", ""if_demo_available"": ""feature-interactive-demo""}",Complex onboarding flow + Cluttered layout,HIGH
3,E-commerce,Feature-Rich Showcase,Vibrant & Block-based,Brand primary + Success green,Engaging + Clear hierarchy,Card hover lift (200ms) + Scale effect,"{""if_luxury"": ""switch-to-liquid-glass"", ""if_conversion_focused"": ""add-urgency-colors""}",Flat design without depth + Text-heavy pages,HIGH
4,E-commerce Luxury,Feature-Rich Showcase,Liquid Glass + Glassmorphism,Premium colors + Minimal accent,Elegant + Refined typography,Chromatic aberration + Fluid animations (400-600ms),"{""if_checkout"": ""emphasize-trust"", ""if_hero_needed"": ""use-3d-hyperrealism""}",Vibrant & Block-based + Playful colors,HIGH
5,Healthcare App,Social Proof-Focused,Neumorphism + Accessible & Ethical,Calm blue + Health green,Readable + Large type (16px+),Soft box-shadow + Smooth press (150ms),"{""must_have"": ""wcag-aaa-compliance"", ""if_medication"": ""red-alert-colors""}",Bright neon colors + Motion-heavy animations + AI purple/pink gradients,HIGH
6,Fintech/Crypto,Conversion-Optimized,Glassmorphism + Dark Mode (OLED),Dark tech colors + Vibrant accents,Modern + Confident typography,Real-time chart animations + Alert pulse/glow,"{""must_have"": ""security-badges"", ""if_real_time"": ""add-streaming-data""}",Light backgrounds + No security indicators,HIGH
7,Education,Feature-Rich Showcase,Claymorphism + Micro-interactions,Playful colors + Clear hierarchy,Friendly + Engaging typography,Soft press (200ms) + Fluffy elements,"{""if_gamification"": ""add-progress-animation"", ""if_children"": ""increase-playfulness""}",Dark modes + Complex jargon,MEDIUM
8,Portfolio/Personal,Storytelling-Driven,Motion-Driven + Minimalism,Brand primary + Artistic,Expressive + Variable typography,Parallax (3-5 layers) + Scroll-triggered reveals,"{""if_creative_field"": ""add-brutalism"", ""if_minimal_portfolio"": ""reduce-motion""}",Corporate templates + Generic layouts,MEDIUM
9,Government/Public,Minimal & Direct,Accessible & Ethical + Minimalism,Professional blue + High contrast,Clear + Large typography,Clear focus rings (3-4px) + Skip links,"{""must_have"": ""wcag-aaa"", ""must_have"": ""keyboard-navigation""}",Ornate design + Low contrast + Motion effects + AI purple/pink gradients,HIGH
10,Fintech (Banking),Trust & Authority,Minimalism + Accessible & Ethical,Navy + Trust Blue + Gold,Professional + Trustworthy,Smooth state transitions + Number animations,"{""must_have"": ""security-first"", ""if_dashboard"": ""use-dark-mode""}",Playful design + Unclear fees + AI purple/pink gradients,HIGH
11,Social Media App,Feature-Rich Showcase,Vibrant & Block-based + Motion-Driven,Vibrant + Engagement colors,Modern + Bold typography,Large scroll animations + Icon animations,"{""if_engagement_metric"": ""add-motion"", ""if_content_focused"": ""minimize-chrome""}",Heavy skeuomorphism + Accessibility ignored,MEDIUM
12,Startup Landing,Hero-Centric + Trust,Motion-Driven + Vibrant & Block,Bold primaries + Accent contrast,Modern + Energetic typography,Scroll-triggered animations + Parallax,"{""if_pre_launch"": ""use-waitlist-pattern"", ""if_video_ready"": ""add-hero-video""}",Static design + No video + Poor mobile,HIGH
13,Gaming,Feature-Rich Showcase,3D & Hyperrealism + Retro-Futurism,Vibrant + Neon + Immersive,Bold + Impactful typography,WebGL 3D rendering + Glitch effects,"{""if_competitive"": ""add-real-time-stats"", ""if_casual"": ""increase-playfulness""}",Minimalist design + Static assets,HIGH
14,Creative Agency,Storytelling-Driven,Brutalism + Motion-Driven,Bold primaries + Artistic freedom,Bold + Expressive typography,CRT scanlines + Neon glow + Glitch effects,"{""must_have"": ""case-studies"", ""if_boutique"": ""increase-artistic-freedom""}",Corporate minimalism + Hidden portfolio,HIGH
15,Wellness/Mental Health,Social Proof-Focused,Neumorphism + Accessible & Ethical,Calm Pastels + Trust colors,Calming + Readable typography,Soft press + Breathing animations,"{""must_have"": ""privacy-first"", ""if_meditation"": ""add-breathing-animation""}",Bright neon + Motion overload,HIGH
16,Restaurant/Food,Hero-Centric + Conversion,Vibrant & Block-based + Motion-Driven,Warm colors (Orange Red Brown),Appetizing + Clear typography,Food image reveal + Menu hover effects,"{""must_have"": ""high_quality_images"", ""if_delivery"": ""emphasize-speed""}",Low-quality imagery + Outdated hours,HIGH
17,Real Estate,Hero-Centric + Feature-Rich,Glassmorphism + Minimalism,Trust Blue + Gold + White,Professional + Confident,3D property tour zoom + Map hover,"{""if_luxury"": ""add-3d-models"", ""must_have"": ""map-integration""}",Poor photos + No virtual tours,HIGH
18,Travel/Tourism,Storytelling-Driven + Hero,Aurora UI + Motion-Driven,Vibrant destination + Sky Blue,Inspirational + Engaging,Destination parallax + Itinerary animations,"{""if_experience_focused"": ""use-storytelling"", ""must_have"": ""mobile-booking""}",Generic photos + Complex booking,HIGH
19,SaaS Dashboard,Data-Dense Dashboard,Data-Dense + Heat Map,Cool to Hot gradients + Neutral grey,Clear + Readable typography,Hover tooltips + Chart zoom + Real-time pulse,"{""must_have"": ""real-time-updates"", ""if_large_dataset"": ""prioritize-performance""}",Ornate design + Slow rendering,HIGH
20,B2B SaaS Enterprise,Feature-Rich Showcase,Trust & Authority + Minimal,Professional blue + Neutral grey,Formal + Clear typography,Subtle section transitions + Feature reveals,"{""must_have"": ""case-studies"", ""must_have"": ""roi-messaging""}",Playful design + Hidden features + AI purple/pink gradients,HIGH
21,Music/Entertainment,Feature-Rich Showcase,Dark Mode (OLED) + Vibrant & Block-based,Dark (#121212) + Vibrant accents + Album art colors,Modern + Bold typography,Waveform visualization + Playlist animations,"{""must_have"": ""audio-player-ux"", ""if_discovery_focused"": ""add-playlist-recommendations""}",Cluttered layout + Poor audio player UX,HIGH
22,Video Streaming/OTT,Hero-Centric + Feature-Rich,Dark Mode (OLED) + Motion-Driven,Dark bg + Poster colors + Brand accent,Bold + Engaging typography,Video player animations + Content carousel (parallax),"{""must_have"": ""continue-watching"", ""if_personalized"": ""add-recommendations""}",Static layout + Slow video player,HIGH
23,Job Board/Recruitment,Conversion-Optimized + Feature-Rich,Flat Design + Minimalism,Professional Blue + Success Green + Neutral,Clear + Professional typography,Search/filter animations + Application flow,"{""must_have"": ""advanced-search"", ""if_salary_focused"": ""highlight-compensation""}",Outdated forms + Hidden filters,HIGH
24,Marketplace (P2P),Feature-Rich Showcase + Social Proof,Vibrant & Block-based + Flat Design,Trust colors + Category colors + Success green,Modern + Engaging typography,Review star animations + Listing hover effects,"{""must_have"": ""seller-profiles"", ""must_have"": ""secure-payment""}",Low trust signals + Confusing layout,HIGH
25,Logistics/Delivery,Feature-Rich Showcase + Real-Time,Minimalism + Flat Design,Blue (#2563EB) + Orange (tracking) + Green,Clear + Functional typography,Real-time tracking animation + Status pulse,"{""must_have"": ""tracking-map"", ""must_have"": ""delivery-updates""}",Static tracking + No map integration + AI purple/pink gradients,HIGH
26,Agriculture/Farm Tech,Feature-Rich Showcase,Organic Biophilic + Flat Design,Earth Green (#4A7C23) + Brown + Sky Blue,Clear + Informative typography,Data visualization + Weather animations,"{""must_have"": ""sensor-dashboard"", ""if_crop_focused"": ""add-health-indicators""}",Generic design + Ignored accessibility + AI purple/pink gradients,MEDIUM
27,Construction/Architecture,Hero-Centric + Feature-Rich,Minimalism + 3D & Hyperrealism,Grey (#4A4A4A) + Orange (safety) + Blueprint Blue,Professional + Bold typography,3D model viewer + Timeline animations,"{""must_have"": ""project-portfolio"", ""if_team_collaboration"": ""add-real-time-updates""}",2D-only layouts + Poor image quality + AI purple/pink gradients,HIGH
28,Automotive/Car Dealership,Hero-Centric + Feature-Rich,Motion-Driven + 3D & Hyperrealism,Brand colors + Metallic + Dark/Light,Bold + Confident typography,360 product view + Configurator animations,"{""must_have"": ""vehicle-comparison"", ""must_have"": ""financing-calculator""}",Static product pages + Poor UX,HIGH
29,Photography Studio,Storytelling-Driven + Hero-Centric,Motion-Driven + Minimalism,Black + White + Minimal accent,Elegant + Minimal typography,Full-bleed gallery + Before/after reveal,"{""must_have"": ""portfolio-showcase"", ""if_booking"": ""add-calendar-system""}",Heavy text + Poor image showcase,HIGH
30,Coworking Space,Hero-Centric + Feature-Rich,Vibrant & Block-based + Glassmorphism,Energetic colors + Wood tones + Brand,Modern + Engaging typography,Space tour video + Amenity reveal animations,"{""must_have"": ""virtual-tour"", ""must_have"": ""booking-system""}",Outdated photos + Confusing layout,MEDIUM
31,Cleaning Service,Conversion-Optimized + Trust,Soft UI Evolution + Flat Design,Fresh Blue (#00B4D8) + Clean White + Green,Friendly + Clear typography,Before/after gallery + Service package reveal,"{""must_have"": ""price-transparency"", ""must_have"": ""trust-badges""}",Poor before/after imagery + Hidden pricing,HIGH
32,Home Services,Conversion-Optimized + Trust,Flat Design + Trust & Authority,Trust Blue + Safety Orange + Grey,Professional + Clear typography,Emergency contact highlight + Service menu animations,"{""must_have"": ""emergency-contact"", ""must_have"": ""certifications-display""}",Hidden contact info + No certifications,HIGH
33,Childcare/Daycare,Social Proof-Focused + Trust,Claymorphism + Vibrant & Block-based,Playful pastels + Safe colors + Warm,Friendly + Playful typography,Parent portal animations + Activity gallery reveal,"{""must_have"": ""parent-communication"", ""must_have"": ""safety-certifications""}",Generic design + Hidden safety info,HIGH
34,Senior Care/Elderly,Trust & Authority + Accessible,Accessible & Ethical + Soft UI Evolution,Calm Blue + Warm neutrals + Large text,Large + Clear typography (18px+),Large touch targets + Clear navigation,"{""must_have"": ""wcag-aaa"", ""must_have"": ""family-portal""}",Small text + Complex navigation + AI purple/pink gradients,HIGH
35,Medical Clinic,Trust & Authority + Conversion,Accessible & Ethical + Minimalism,Medical Blue (#0077B6) + Trust White,Professional + Readable typography,Online booking flow + Doctor profile reveals,"{""must_have"": ""appointment-booking"", ""must_have"": ""insurance-info""}",Outdated interface + Confusing booking + AI purple/pink gradients,HIGH
36,Pharmacy/Drug Store,Conversion-Optimized + Trust,Flat Design + Accessible & Ethical,Pharmacy Green + Trust Blue + Clean White,Clear + Functional typography,Prescription upload flow + Refill reminders,"{""must_have"": ""prescription-management"", ""must_have"": ""drug-interaction-warnings""}",Confusing layout + Privacy concerns + AI purple/pink gradients,HIGH
37,Dental Practice,Social Proof-Focused + Conversion,Soft UI Evolution + Minimalism,Fresh Blue + White + Smile Yellow,Friendly + Professional typography,Before/after gallery + Patient testimonial carousel,"{""must_have"": ""before-after-gallery"", ""must_have"": ""appointment-system""}",Poor imagery + No testimonials,HIGH
38,Veterinary Clinic,Social Proof-Focused + Trust,Claymorphism + Accessible & Ethical,Caring Blue + Pet colors + Warm,Friendly + Welcoming typography,Pet profile management + Service animations,"{""must_have"": ""pet-portal"", ""must_have"": ""emergency-contact""}",Generic design + Hidden services,MEDIUM
39,News/Media Platform,Hero-Centric + Feature-Rich,Minimalism + Flat Design,Brand colors + High contrast,Clear + Readable typography,Breaking news badge + Article reveal animations,"{""must_have"": ""mobile-first-reading"", ""must_have"": ""category-navigation""}",Cluttered layout + Slow loading,HIGH
40,Legal Services,Trust & Authority + Minimal,Trust & Authority + Minimalism,Navy Blue (#1E3A5F) + Gold + White,Professional + Authoritative typography,Practice area reveal + Attorney profile animations,"{""must_have"": ""case-results"", ""must_have"": ""credential-display""}",Outdated design + Hidden credentials + AI purple/pink gradients,HIGH
41,Beauty/Spa/Wellness Service,Hero-Centric + Social Proof,Soft UI Evolution + Neumorphism,Soft pastels (Pink Sage Cream) + Gold accents,Elegant + Calming typography,Soft shadows + Smooth transitions (200-300ms) + Gentle hover,"{""must_have"": ""booking-system"", ""must_have"": ""before-after-gallery"", ""if_luxury"": ""add-gold-accents""}",Bright neon colors + Harsh animations + Dark mode,HIGH
42,Service Landing Page,Hero-Centric + Trust & Authority,Minimalism + Social Proof-Focused,Brand primary + Trust colors,Professional + Clear typography,Testimonial carousel + CTA hover (200ms),"{""must_have"": ""social-proof"", ""must_have"": ""clear-cta""}",Complex navigation + Hidden contact info,HIGH
43,B2B Service,Feature-Rich Showcase + Trust,Trust & Authority + Minimalism,Professional blue + Neutral grey,Formal + Clear typography,Section transitions + Feature reveals,"{""must_have"": ""case-studies"", ""must_have"": ""roi-messaging""}",Playful design + Hidden credentials + AI purple/pink gradients,HIGH
44,Financial Dashboard,Data-Dense Dashboard,Dark Mode (OLED) + Data-Dense,Dark bg + Red/Green alerts + Trust blue,Clear + Readable typography,Real-time number animations + Alert pulse,"{""must_have"": ""real-time-updates"", ""must_have"": ""high-contrast""}",Light mode default + Slow rendering,HIGH
45,Analytics Dashboard,Data-Dense + Drill-Down,Data-Dense + Heat Map,Cool→Hot gradients + Neutral grey,Clear + Functional typography,Hover tooltips + Chart zoom + Filter animations,"{""must_have"": ""data-export"", ""if_large_dataset"": ""virtualize-lists""}",Ornate design + No filtering,HIGH
46,Productivity Tool,Interactive Demo + Feature-Rich,Flat Design + Micro-interactions,Clear hierarchy + Functional colors,Clean + Efficient typography,Quick actions (150ms) + Task animations,"{""must_have"": ""keyboard-shortcuts"", ""if_collaboration"": ""add-real-time-cursors""}",Complex onboarding + Slow performance,HIGH
47,Design System/Component Library,Feature-Rich + Documentation,Minimalism + Accessible & Ethical,Clear hierarchy + Code-like structure,Monospace + Clear typography,Code copy animations + Component previews,"{""must_have"": ""search"", ""must_have"": ""code-examples""}",Poor documentation + No live preview,HIGH
48,AI/Chatbot Platform,Interactive Demo + Minimal,AI-Native UI + Minimalism,Neutral + AI Purple (#6366F1),Modern + Clear typography,Streaming text + Typing indicators + Fade-in,"{""must_have"": ""conversational-ui"", ""must_have"": ""context-awareness""}",Heavy chrome + Slow response feedback,HIGH
49,NFT/Web3 Platform,Feature-Rich Showcase,Cyberpunk UI + Glassmorphism,Dark + Neon + Gold (#FFD700),Bold + Modern typography,Wallet connect animations + Transaction feedback,"{""must_have"": ""wallet-integration"", ""must_have"": ""gas-fees-display""}",Light mode default + No transaction status,HIGH
50,Creator Economy Platform,Social Proof + Feature-Rich,Vibrant & Block-based + Bento Box Grid,Vibrant + Brand colors,Modern + Bold typography,Engagement counter animations + Profile reveals,"{""must_have"": ""creator-profiles"", ""must_have"": ""monetization-display""}",Generic layout + Hidden earnings,MEDIUM
51,Sustainability/ESG Platform,Trust & Authority + Data,Organic Biophilic + Minimalism,Green (#228B22) + Earth tones,Clear + Informative typography,Progress indicators + Impact animations,"{""must_have"": ""data-transparency"", ""must_have"": ""certification-badges""}",Greenwashing visuals + No data,HIGH
52,Remote Work/Collaboration,Feature-Rich + Real-Time,Soft UI Evolution + Minimalism,Calm Blue + Neutral grey,Clean + Readable typography,Real-time presence indicators + Notification badges,"{""must_have"": ""status-indicators"", ""must_have"": ""video-integration""}",Cluttered interface + No presence,HIGH
53,Pet Tech App,Storytelling + Feature-Rich,Claymorphism + Vibrant & Block-based,Playful + Warm colors,Friendly + Playful typography,Pet profile animations + Health tracking charts,"{""must_have"": ""pet-profiles"", ""if_health"": ""add-vet-integration""}",Generic design + No personality,MEDIUM
54,Smart Home/IoT Dashboard,Real-Time Monitoring,Glassmorphism + Dark Mode (OLED),Dark + Status indicator colors,Clear + Functional typography,Device status pulse + Quick action animations,"{""must_have"": ""real-time-controls"", ""must_have"": ""energy-monitoring""}",Slow updates + No automation,HIGH
55,EV/Charging Ecosystem,Hero-Centric + Feature-Rich,Minimalism + Aurora UI,Electric Blue (#009CD1) + Green,Modern + Clear typography,Range estimation animations + Map interactions,"{""must_have"": ""charging-map"", ""must_have"": ""range-calculator""}",Poor map UX + Hidden costs,HIGH
56,Subscription Box Service,Feature-Rich + Conversion,Vibrant & Block-based + Motion-Driven,Brand + Excitement colors,Engaging + Clear typography,Unboxing reveal animations + Product carousel,"{""must_have"": ""personalization-quiz"", ""must_have"": ""subscription-management""}",Confusing pricing + No unboxing preview,HIGH
57,Podcast Platform,Storytelling + Feature-Rich,Dark Mode (OLED) + Minimalism,Dark + Audio waveform accents,Modern + Clear typography,Waveform visualizations + Episode transitions,"{""must_have"": ""audio-player-ux"", ""must_have"": ""episode-discovery""}",Poor audio player + Cluttered layout,HIGH
58,Dating App,Social Proof + Feature-Rich,Vibrant & Block-based + Motion-Driven,Warm + Romantic (Pink/Red gradients),Modern + Friendly typography,Profile card swipe + Match animations,"{""must_have"": ""profile-cards"", ""must_have"": ""safety-features""}",Generic profiles + No safety,HIGH
59,Micro-Credentials/Badges,Trust & Authority + Feature,Minimalism + Flat Design,Trust Blue + Gold (#FFD700),Professional + Clear typography,Badge reveal animations + Progress tracking,"{""must_have"": ""credential-verification"", ""must_have"": ""progress-display""}",No verification + Hidden progress,MEDIUM
60,Knowledge Base/Documentation,FAQ + Minimal,Minimalism + Accessible & Ethical,Clean hierarchy + Minimal color,Clear + Readable typography,Search highlight + Smooth scrolling,"{""must_have"": ""search-first"", ""must_have"": ""version-switching""}",Poor navigation + No search,HIGH
61,Hyperlocal Services,Conversion + Feature-Rich,Minimalism + Vibrant & Block-based,Location markers + Trust colors,Clear + Functional typography,Map hover + Provider card reveals,"{""must_have"": ""map-integration"", ""must_have"": ""booking-system""}",No map + Hidden reviews,HIGH
62,Luxury/Premium Brand,Storytelling + Feature-Rich,Liquid Glass + Glassmorphism,Black + Gold (#FFD700) + White,Elegant + Refined typography,Slow parallax + Premium reveals (400-600ms),"{""must_have"": ""high-quality-imagery"", ""must_have"": ""storytelling""}",Cheap visuals + Fast animations,HIGH
63,Fitness/Gym App,Feature-Rich + Data,Vibrant & Block-based + Dark Mode (OLED),Energetic (Orange #FF6B35) + Dark bg,Bold + Motivational typography,Progress ring animations + Achievement unlocks,"{""must_have"": ""progress-tracking"", ""must_have"": ""workout-plans""}",Static design + No gamification,HIGH
64,Hotel/Hospitality,Hero-Centric + Social Proof,Liquid Glass + Minimalism,Warm neutrals + Gold (#D4AF37),Elegant + Welcoming typography,Room gallery + Amenity reveals,"{""must_have"": ""room-booking"", ""must_have"": ""virtual-tour""}",Poor photos + Complex booking,HIGH
65,Wedding/Event Planning,Storytelling + Social Proof,Soft UI Evolution + Aurora UI,Soft Pink (#FFD6E0) + Gold + Cream,Elegant + Romantic typography,Gallery reveals + Timeline animations,"{""must_have"": ""portfolio-gallery"", ""must_have"": ""planning-tools""}",Generic templates + No portfolio,HIGH
66,Insurance Platform,Conversion + Trust,Trust & Authority + Flat Design,Trust Blue (#0066CC) + Green + Neutral,Clear + Professional typography,Quote calculator animations + Policy comparison,"{""must_have"": ""quote-calculator"", ""must_have"": ""policy-comparison""}",Confusing pricing + No trust signals + AI purple/pink gradients,HIGH
67,Banking/Traditional Finance,Trust & Authority + Feature,Minimalism + Accessible & Ethical,Navy (#0A1628) + Trust Blue + Gold,Professional + Trustworthy typography,Smooth number animations + Security indicators,"{""must_have"": ""security-first"", ""must_have"": ""accessibility""}",Playful design + Poor security UX + AI purple/pink gradients,HIGH
68,Online Course/E-learning,Feature-Rich + Social Proof,Claymorphism + Vibrant & Block-based,Vibrant learning colors + Progress green,Friendly + Engaging typography,Progress bar animations + Certificate reveals,"{""must_have"": ""progress-tracking"", ""must_have"": ""video-player""}",Boring design + No gamification,HIGH
69,Non-profit/Charity,Storytelling + Trust,Accessible & Ethical + Organic Biophilic,Cause-related colors + Trust + Warm,Heartfelt + Readable typography,Impact counter animations + Story reveals,"{""must_have"": ""impact-stories"", ""must_have"": ""donation-transparency""}",No impact data + Hidden financials,HIGH
70,Florist/Plant Shop,Hero-Centric + Conversion,Organic Biophilic + Vibrant & Block-based,Natural Green + Floral pinks/purples,Elegant + Natural typography,Product reveal + Seasonal transitions,"{""must_have"": ""delivery-scheduling"", ""must_have"": ""care-guides""}",Poor imagery + No seasonal content,MEDIUM
71,Bakery/Cafe,Hero-Centric + Conversion,Vibrant & Block-based + Soft UI Evolution,Warm Brown + Cream + Appetizing accents,Warm + Inviting typography,Menu hover + Order animations,"{""must_have"": ""menu-display"", ""must_have"": ""online-ordering""}",Poor food photos + Hidden hours,HIGH
72,Coffee Shop,Hero-Centric + Minimal,Minimalism + Organic Biophilic,Coffee Brown (#6F4E37) + Cream + Warm,Cozy + Clean typography,Menu transitions + Loyalty animations,"{""must_have"": ""menu"", ""if_loyalty"": ""add-rewards-system""}",Generic design + No atmosphere,MEDIUM
73,Brewery/Winery,Storytelling + Hero-Centric,Motion-Driven + Storytelling-Driven,Deep amber/burgundy + Gold + Craft,Artisanal + Heritage typography,Tasting note reveals + Heritage timeline,"{""must_have"": ""product-showcase"", ""must_have"": ""story-heritage""}",Generic product pages + No story,HIGH
74,Airline,Conversion + Feature-Rich,Minimalism + Glassmorphism,Sky Blue + Brand colors + Trust,Clear + Professional typography,Flight search animations + Boarding pass reveals,"{""must_have"": ""flight-search"", ""must_have"": ""mobile-first""}",Complex booking + Poor mobile,HIGH
75,Magazine/Blog,Storytelling + Hero-Centric,Swiss Modernism 2.0 + Motion-Driven,Editorial colors + Brand + Clean white,Editorial + Elegant typography,Article transitions + Category reveals,"{""must_have"": ""article-showcase"", ""must_have"": ""newsletter-signup""}",Poor typography + Slow loading,HIGH
76,Freelancer Platform,Feature-Rich + Conversion,Flat Design + Minimalism,Professional Blue + Success Green,Clear + Professional typography,Skill match animations + Review reveals,"{""must_have"": ""portfolio-display"", ""must_have"": ""skill-matching""}",Poor profiles + No reviews,HIGH
77,Consulting Firm,Trust & Authority + Minimal,Trust & Authority + Minimalism,Navy + Gold + Professional grey,Authoritative + Clear typography,Case study reveals + Team profiles,"{""must_have"": ""case-studies"", ""must_have"": ""thought-leadership""}",Generic content + No credentials + AI purple/pink gradients,HIGH
78,Marketing Agency,Storytelling + Feature-Rich,Brutalism + Motion-Driven,Bold brand colors + Creative freedom,Bold + Expressive typography,Portfolio reveals + Results animations,"{""must_have"": ""portfolio"", ""must_have"": ""results-metrics""}",Boring design + Hidden work,HIGH
79,Event Management,Hero-Centric + Feature-Rich,Vibrant & Block-based + Motion-Driven,Event theme colors + Excitement accents,Bold + Engaging typography,Countdown timer + Registration flow,"{""must_have"": ""registration"", ""must_have"": ""agenda-display""}",Confusing registration + No countdown,HIGH
80,Conference/Webinar Platform,Feature-Rich + Conversion,Glassmorphism + Minimalism,Professional Blue + Video accent,Professional + Clear typography,Live stream integration + Agenda transitions,"{""must_have"": ""registration"", ""must_have"": ""speaker-profiles""}",Poor video UX + No networking,HIGH
81,Membership/Community,Social Proof + Conversion,Vibrant & Block-based + Soft UI Evolution,Community brand colors + Engagement,Friendly + Engaging typography,Member counter + Benefit reveals,"{""must_have"": ""member-benefits"", ""must_have"": ""pricing-tiers""}",Hidden benefits + No community proof,HIGH
82,Newsletter Platform,Minimal + Conversion,Minimalism + Flat Design,Brand primary + Clean white + CTA,Clean + Readable typography,Subscribe form + Archive reveals,"{""must_have"": ""subscribe-form"", ""must_have"": ""sample-content""}",Complex signup + No preview,MEDIUM
83,Digital Products/Downloads,Feature-Rich + Conversion,Vibrant & Block-based + Motion-Driven,Product colors + Brand + Success green,Modern + Clear typography,Product preview + Instant delivery animations,"{""must_have"": ""product-preview"", ""must_have"": ""instant-delivery""}",No preview + Slow delivery,HIGH
84,Church/Religious Organization,Hero-Centric + Social Proof,Accessible & Ethical + Soft UI Evolution,Warm Gold + Deep Purple/Blue + White,Welcoming + Clear typography,Service time highlights + Event calendar,"{""must_have"": ""service-times"", ""must_have"": ""community-events""}",Outdated design + Hidden info,MEDIUM
85,Sports Team/Club,Hero-Centric + Feature-Rich,Vibrant & Block-based + Motion-Driven,Team colors + Energetic accents,Bold + Impactful typography,Score animations + Schedule reveals,"{""must_have"": ""schedule"", ""must_have"": ""roster""}",Static content + Poor fan engagement,HIGH
86,Museum/Gallery,Storytelling + Feature-Rich,Minimalism + Motion-Driven,Art-appropriate neutrals + Exhibition accents,Elegant + Minimal typography,Virtual tour + Collection reveals,"{""must_have"": ""virtual-tour"", ""must_have"": ""exhibition-info""}",Cluttered layout + No online access,HIGH
87,Theater/Cinema,Hero-Centric + Conversion,Dark Mode (OLED) + Motion-Driven,Dark + Spotlight accents + Gold,Dramatic + Bold typography,Seat selection + Trailer reveals,"{""must_have"": ""showtimes"", ""must_have"": ""seat-selection""}",Poor booking UX + No trailers,HIGH
88,Language Learning App,Feature-Rich + Social Proof,Claymorphism + Vibrant & Block-based,Playful colors + Progress indicators,Friendly + Clear typography,Progress animations + Achievement unlocks,"{""must_have"": ""progress-tracking"", ""must_have"": ""gamification""}",Boring design + No motivation,HIGH
89,Coding Bootcamp,Feature-Rich + Social Proof,Dark Mode (OLED) + Minimalism,Code editor colors + Brand + Success,Technical + Clear typography,Terminal animations + Career outcome reveals,"{""must_have"": ""curriculum"", ""must_have"": ""career-outcomes""}",Light mode only + Hidden results,HIGH
90,Cybersecurity Platform,Trust & Authority + Real-Time,Cyberpunk UI + Dark Mode (OLED),Matrix Green (#00FF00) + Deep Black,Technical + Clear typography,Threat visualization + Alert animations,"{""must_have"": ""real-time-monitoring"", ""must_have"": ""threat-display""}",Light mode + Poor data viz,HIGH
91,Developer Tool/IDE,Minimal + Documentation,Dark Mode (OLED) + Minimalism,Dark syntax theme + Blue focus,Monospace + Functional typography,Syntax highlighting + Command palette,"{""must_have"": ""keyboard-shortcuts"", ""must_have"": ""documentation""}",Light mode default + Slow performance,HIGH
92,Biotech/Life Sciences,Storytelling + Data,Glassmorphism + Clean Science,Sterile White + DNA Blue + Life Green,Scientific + Clear typography,Data visualization + Research reveals,"{""must_have"": ""data-accuracy"", ""must_have"": ""clean-aesthetic""}",Cluttered data + Poor credibility,HIGH
93,Space Tech/Aerospace,Immersive + Feature-Rich,Holographic/HUD + Dark Mode,Deep Space Black + Star White + Metallic,Futuristic + Precise typography,Telemetry animations + 3D renders,"{""must_have"": ""high-tech-feel"", ""must_have"": ""precision-data""}",Generic design + No immersion,HIGH
94,Architecture/Interior,Portfolio + Hero-Centric,Exaggerated Minimalism + High Imagery,Monochrome + Gold Accent + High Imagery,Architectural + Elegant typography,Project gallery + Blueprint reveals,"{""must_have"": ""high-res-images"", ""must_have"": ""project-portfolio""}",Poor imagery + Cluttered layout,HIGH
95,Quantum Computing,Immersive + Interactive,Holographic/HUD + Dark Mode,Quantum Blue (#00FFFF) + Deep Black,Futuristic + Scientific typography,Probability visualizations + Qubit state animations,"{""must_have"": ""complexity-visualization"", ""must_have"": ""scientific-credibility""}",Generic tech design + No viz,HIGH
96,Biohacking/Longevity App,Data-Dense + Storytelling,Biomimetic/Organic 2.0 + Minimalism,Cellular Pink/Red + DNA Blue + White,Scientific + Clear typography,Biological data viz + Progress animations,"{""must_have"": ""data-privacy"", ""must_have"": ""scientific-credibility""}",Generic health app + No privacy,HIGH
97,Autonomous Drone Fleet,Real-Time + Feature-Rich,HUD/Sci-Fi FUI + Real-Time,Tactical Green + Alert Red + Map Dark,Technical + Functional typography,Telemetry animations + 3D spatial awareness,"{""must_have"": ""real-time-telemetry"", ""must_have"": ""safety-alerts""}",Slow updates + Poor spatial viz,HIGH
98,Generative Art Platform,Showcase + Feature-Rich,Minimalism + Gen Z Chaos,Neutral (#F5F5F5) + User Content,Minimal + Content-focused typography,Gallery masonry + Minting animations,"{""must_have"": ""fast-loading"", ""must_have"": ""creator-attribution""}",Heavy chrome + Slow loading,HIGH
99,Spatial Computing OS,Immersive + Interactive,Spatial UI (VisionOS) + Glassmorphism,Frosted Glass + System Colors + Depth,Spatial + Readable typography,Depth hierarchy + Gaze interactions,"{""must_have"": ""depth-hierarchy"", ""must_have"": ""environment-awareness""}",2D design + No spatial depth,HIGH
100,Sustainable Energy/Climate,Data + Trust,Organic Biophilic + E-Ink/Paper,Earth Green + Sky Blue + Solar Yellow,Clear + Informative typography,Impact viz + Progress animations,"{""must_have"": ""data-transparency"", ""must_have"": ""impact-visualization""}",Greenwashing + No real data,HIGH
.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
No,Category,Issue,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity
1,Navigation,Smooth Scroll,Web,Anchor links should scroll smoothly to target section,Use scroll-behavior: smooth on html element,Jump directly without transition,html { scroll-behavior: smooth; },<a href='#section'> without CSS,High
2,Navigation,Sticky Navigation,Web,Fixed nav should not obscure content,Add padding-top to body equal to nav height,Let nav overlap first section content,pt-20 (if nav is h-20),No padding compensation,Medium
3,Navigation,Active State,All,Current page/section should be visually indicated,Highlight active nav item with color/underline,No visual feedback on current location,text-primary border-b-2,All links same style,Medium
4,Navigation,Back Button,Mobile,Users expect back to work predictably,Preserve navigation history properly,Break browser/app back button behavior,history.pushState(),location.replace(),High
5,Navigation,Deep Linking,All,URLs should reflect current state for sharing,Update URL on state/view changes,Static URLs for dynamic content,Use query params or hash,Single URL for all states,Medium
6,Navigation,Breadcrumbs,Web,Show user location in site hierarchy,Use for sites with 3+ levels of depth,Use for flat single-level sites,Home > Category > Product,Only on deep nested pages,Low
7,Animation,Excessive Motion,All,Too many animations cause distraction and motion sickness,Animate 1-2 key elements per view maximum,Animate everything that moves,Single hero animation,animate-bounce on 5+ elements,High
8,Animation,Duration Timing,All,Animations should feel responsive not sluggish,Use 150-300ms for micro-interactions,Use animations longer than 500ms for UI,transition-all duration-200,duration-1000,Medium
9,Animation,Reduced Motion,All,Respect user's motion preferences,Check prefers-reduced-motion media query,Ignore accessibility motion settings,@media (prefers-reduced-motion: reduce),No motion query check,High
10,Animation,Loading States,All,Show feedback during async operations,Use skeleton screens or spinners,Leave UI frozen with no feedback,animate-pulse skeleton,Blank screen while loading,High
11,Animation,Hover vs Tap,All,Hover effects don't work on touch devices,Use click/tap for primary interactions,Rely only on hover for important actions,onClick handler,onMouseEnter only,High
12,Animation,Continuous Animation,All,Infinite animations are distracting,Use for loading indicators only,Use for decorative elements,animate-spin on loader,animate-bounce on icons,Medium
13,Animation,Transform Performance,Web,Some CSS properties trigger expensive repaints,Use transform and opacity for animations,Animate width/height/top/left properties,transform: translateY(),top: 10px animation,Medium
14,Animation,Easing Functions,All,Linear motion feels robotic,Use ease-out for entering ease-in for exiting,Use linear for UI transitions,ease-out,linear,Low
15,Layout,Z-Index Management,Web,Stacking context conflicts cause hidden elements,Define z-index scale system (10 20 30 50),Use arbitrary large z-index values,z-10 z-20 z-50,z-[9999],High
16,Layout,Overflow Hidden,Web,Hidden overflow can clip important content,Test all content fits within containers,Blindly apply overflow-hidden,overflow-auto with scroll,overflow-hidden truncating content,Medium
17,Layout,Fixed Positioning,Web,Fixed elements can overlap or be inaccessible,Account for safe areas and other fixed elements,Stack multiple fixed elements carelessly,Fixed nav + fixed bottom with gap,Multiple overlapping fixed elements,Medium
18,Layout,Stacking Context,Web,New stacking contexts reset z-index,Understand what creates new stacking context,Expect z-index to work across contexts,Parent with z-index isolates children,z-index: 9999 not working,Medium
19,Layout,Content Jumping,Web,Layout shift when content loads is jarring,Reserve space for async content,Let images/content push layout around,aspect-ratio or fixed height,No dimensions on images,High
20,Layout,Viewport Units,Web,100vh can be problematic on mobile browsers,Use dvh or account for mobile browser chrome,Use 100vh for full-screen mobile layouts,min-h-dvh or min-h-screen,h-screen on mobile,Medium
21,Layout,Container Width,Web,Content too wide is hard to read,Limit max-width for text content (65-75ch),Let text span full viewport width,max-w-prose or max-w-3xl,Full width paragraphs,Medium
22,Touch,Touch Target Size,Mobile,Small buttons are hard to tap accurately,Minimum 44x44px touch targets,Tiny clickable areas,min-h-[44px] min-w-[44px],w-6 h-6 buttons,High
23,Touch,Touch Spacing,Mobile,Adjacent touch targets need adequate spacing,Minimum 8px gap between touch targets,Tightly packed clickable elements,gap-2 between buttons,gap-0 or gap-1,Medium
24,Touch,Gesture Conflicts,Mobile,Custom gestures can conflict with system,Avoid horizontal swipe on main content,Override system gestures,Vertical scroll primary,Horizontal swipe carousel only,Medium
25,Touch,Tap Delay,Mobile,300ms tap delay feels laggy,Use touch-action CSS or fastclick,Default mobile tap handling,touch-action: manipulation,No touch optimization,Medium
26,Touch,Pull to Refresh,Mobile,Accidental refresh is frustrating,Disable where not needed,Enable by default everywhere,overscroll-behavior: contain,Default overscroll,Low
27,Touch,Haptic Feedback,Mobile,Tactile feedback improves interaction feel,Use for confirmations and important actions,Overuse vibration feedback,navigator.vibrate(10),Vibrate on every tap,Low
28,Interaction,Focus States,All,Keyboard users need visible focus indicators,Use visible focus rings on interactive elements,Remove focus outline without replacement,focus:ring-2 focus:ring-blue-500,outline-none without alternative,High
29,Interaction,Hover States,Web,Visual feedback on interactive elements,Change cursor and add subtle visual change,No hover feedback on clickable elements,hover:bg-gray-100 cursor-pointer,No hover style,Medium
30,Interaction,Active States,All,Show immediate feedback on press/click,Add pressed/active state visual change,No feedback during interaction,active:scale-95,No active state,Medium
31,Interaction,Disabled States,All,Clearly indicate non-interactive elements,Reduce opacity and change cursor,Confuse disabled with normal state,opacity-50 cursor-not-allowed,Same style as enabled,Medium
32,Interaction,Loading Buttons,All,Prevent double submission during async actions,Disable button and show loading state,Allow multiple clicks during processing,disabled={loading} spinner,Button clickable while loading,High
33,Interaction,Error Feedback,All,Users need to know when something fails,Show clear error messages near problem,Silent failures with no feedback,Red border + error message,No indication of error,High
34,Interaction,Success Feedback,All,Confirm successful actions to users,Show success message or visual change,No confirmation of completed action,Toast notification or checkmark,Action completes silently,Medium
35,Interaction,Confirmation Dialogs,All,Prevent accidental destructive actions,Confirm before delete/irreversible actions,Delete without confirmation,Are you sure modal,Direct delete on click,High
36,Accessibility,Color Contrast,All,Text must be readable against background,Minimum 4.5:1 ratio for normal text,Low contrast text,#333 on white (7:1),#999 on white (2.8:1),High
37,Accessibility,Color Only,All,Don't convey information by color alone,Use icons/text in addition to color,Red/green only for error/success,Red text + error icon,Red border only for error,High
38,Accessibility,Alt Text,All,Images need text alternatives,Descriptive alt text for meaningful images,Empty or missing alt attributes,alt='Dog playing in park',alt='' for content images,High
39,Accessibility,Heading Hierarchy,Web,Screen readers use headings for navigation,Use sequential heading levels h1-h6,Skip heading levels or misuse for styling,h1 then h2 then h3,h1 then h4,Medium
40,Accessibility,ARIA Labels,All,Interactive elements need accessible names,Add aria-label for icon-only buttons,Icon buttons without labels,aria-label='Close menu',<button><Icon/></button>,High
41,Accessibility,Keyboard Navigation,Web,All functionality accessible via keyboard,Tab order matches visual order,Keyboard traps or illogical tab order,tabIndex for custom order,Unreachable elements,High
42,Accessibility,Screen Reader,All,Content should make sense when read aloud,Use semantic HTML and ARIA properly,Div soup with no semantics,<nav> <main> <article>,<div> for everything,Medium
43,Accessibility,Form Labels,All,Inputs must have associated labels,Use label with for attribute or wrap input,Placeholder-only inputs,<label for='email'>,placeholder='Email' only,High
44,Accessibility,Error Messages,All,Error messages must be announced,Use aria-live or role=alert for errors,Visual-only error indication,role='alert',Red border only,High
45,Accessibility,Skip Links,Web,Allow keyboard users to skip navigation,Provide skip to main content link,No skip link on nav-heavy pages,Skip to main content link,100 tabs to reach content,Medium
46,Performance,Image Optimization,All,Large images slow page load,Use appropriate size and format (WebP),Unoptimized full-size images,srcset with multiple sizes,4000px image for 400px display,High
47,Performance,Lazy Loading,All,Load content as needed,Lazy load below-fold images and content,Load everything upfront,loading='lazy',All images eager load,Medium
48,Performance,Code Splitting,Web,Large bundles slow initial load,Split code by route/feature,Single large bundle,dynamic import(),All code in main bundle,Medium
49,Performance,Caching,Web,Repeat visits should be fast,Set appropriate cache headers,No caching strategy,Cache-Control headers,Every request hits server,Medium
50,Performance,Font Loading,Web,Web fonts can block rendering,Use font-display swap or optional,Invisible text during font load,font-display: swap,FOIT (Flash of Invisible Text),Medium
51,Performance,Third Party Scripts,Web,External scripts can block rendering,Load non-critical scripts async/defer,Synchronous third-party scripts,async or defer attribute,<script src='...'> in head,Medium
52,Performance,Bundle Size,Web,Large JavaScript slows interaction,Monitor and minimize bundle size,Ignore bundle size growth,Bundle analyzer,No size monitoring,Medium
53,Performance,Render Blocking,Web,CSS/JS can block first paint,Inline critical CSS defer non-critical,Large blocking CSS files,Critical CSS inline,All CSS in head,Medium
54,Forms,Input Labels,All,Every input needs a visible label,Always show label above or beside input,Placeholder as only label,<label>Email</label><input>,placeholder='Email' only,High
55,Forms,Error Placement,All,Errors should appear near the problem,Show error below related input,Single error message at top of form,Error under each field,All errors at form top,Medium
56,Forms,Inline Validation,All,Validate as user types or on blur,Validate on blur for most fields,Validate only on submit,onBlur validation,Submit-only validation,Medium
57,Forms,Input Types,All,Use appropriate input types,Use email tel number url etc,Text input for everything,type='email',type='text' for email,Medium
58,Forms,Autofill Support,Web,Help browsers autofill correctly,Use autocomplete attribute properly,Block or ignore autofill,autocomplete='email',autocomplete='off' everywhere,Medium
59,Forms,Required Indicators,All,Mark required fields clearly,Use asterisk or (required) text,No indication of required fields,* required indicator,Guess which are required,Medium
60,Forms,Password Visibility,All,Let users see password while typing,Toggle to show/hide password,No visibility toggle,Show/hide password button,Password always hidden,Medium
61,Forms,Submit Feedback,All,Confirm form submission status,Show loading then success/error state,No feedback after submit,Loading -> Success message,Button click with no response,High
62,Forms,Input Affordance,All,Inputs should look interactive,Use distinct input styling,Inputs that look like plain text,Border/background on inputs,Borderless inputs,Medium
63,Forms,Mobile Keyboards,Mobile,Show appropriate keyboard for input type,Use inputmode attribute,Default keyboard for all inputs,inputmode='numeric',Text keyboard for numbers,Medium
64,Responsive,Mobile First,Web,Design for mobile then enhance for larger,Start with mobile styles then add breakpoints,Desktop-first causing mobile issues,Default mobile + md: lg: xl:,Desktop default + max-width queries,Medium
65,Responsive,Breakpoint Testing,Web,Test at all common screen sizes,Test at 320 375 414 768 1024 1440,Only test on your device,Multiple device testing,Single device development,Medium
66,Responsive,Touch Friendly,Web,Mobile layouts need touch-sized targets,Increase touch targets on mobile,Same tiny buttons on mobile,Larger buttons on mobile,Desktop-sized targets on mobile,High
67,Responsive,Readable Font Size,All,Text must be readable on all devices,Minimum 16px body text on mobile,Tiny text on mobile,text-base or larger,text-xs for body text,High
68,Responsive,Viewport Meta,Web,Set viewport for mobile devices,Use width=device-width initial-scale=1,Missing or incorrect viewport,<meta name='viewport'...>,No viewport meta tag,High
69,Responsive,Horizontal Scroll,Web,Avoid horizontal scrolling,Ensure content fits viewport width,Content wider than viewport,max-w-full overflow-x-hidden,Horizontal scrollbar on mobile,High
70,Responsive,Image Scaling,Web,Images should scale with container,Use max-width: 100% on images,Fixed width images overflow,max-w-full h-auto,width='800' fixed,Medium
71,Responsive,Table Handling,Web,Tables can overflow on mobile,Use horizontal scroll or card layout,Wide tables breaking layout,overflow-x-auto wrapper,Table overflows viewport,Medium
72,Typography,Line Height,All,Adequate line height improves readability,Use 1.5-1.75 for body text,Cramped or excessive line height,leading-relaxed (1.625),leading-none (1),Medium
73,Typography,Line Length,Web,Long lines are hard to read,Limit to 65-75 characters per line,Full-width text on large screens,max-w-prose,Full viewport width text,Medium
74,Typography,Font Size Scale,All,Consistent type hierarchy aids scanning,Use consistent modular scale,Random font sizes,Type scale (12 14 16 18 24 32),Arbitrary sizes,Medium
75,Typography,Font Loading,Web,Fonts should load without layout shift,Reserve space with fallback font,Layout shift when fonts load,font-display: swap + similar fallback,No fallback font,Medium
76,Typography,Contrast Readability,All,Body text needs good contrast,Use darker text on light backgrounds,Gray text on gray background,text-gray-900 on white,text-gray-400 on gray-100,High
77,Typography,Heading Clarity,All,Headings should stand out from body,Clear size/weight difference,Headings similar to body text,Bold + larger size,Same size as body,Medium
78,Feedback,Loading Indicators,All,Show system status during waits,Show spinner/skeleton for operations > 300ms,No feedback during loading,Skeleton or spinner,Frozen UI,High
79,Feedback,Empty States,All,Guide users when no content exists,Show helpful message and action,Blank empty screens,No items yet. Create one!,Empty white space,Medium
80,Feedback,Error Recovery,All,Help users recover from errors,Provide clear next steps,Error without recovery path,Try again button + help link,Error message only,Medium
81,Feedback,Progress Indicators,All,Show progress for multi-step processes,Step indicators or progress bar,No indication of progress,Step 2 of 4 indicator,No step information,Medium
82,Feedback,Toast Notifications,All,Transient messages for non-critical info,Auto-dismiss after 3-5 seconds,Toasts that never disappear,Auto-dismiss toast,Persistent toast,Medium
83,Feedback,Confirmation Messages,All,Confirm successful actions,Brief success message,Silent success,Saved successfully toast,No confirmation,Medium
84,Content,Truncation,All,Handle long content gracefully,Truncate with ellipsis and expand option,Overflow or broken layout,line-clamp-2 with expand,Overflow or cut off,Medium
85,Content,Date Formatting,All,Use locale-appropriate date formats,Use relative or locale-aware dates,Ambiguous date formats,2 hours ago or locale format,01/02/03,Low
86,Content,Number Formatting,All,Format large numbers for readability,Use thousand separators or abbreviations,Long unformatted numbers,"1.2K or 1,234",1234567,Low
87,Content,Placeholder Content,All,Show realistic placeholders during dev,Use realistic sample data,Lorem ipsum everywhere,Real sample content,Lorem ipsum,Low
88,Onboarding,User Freedom,All,Users should be able to skip tutorials,Provide Skip and Back buttons,Force linear unskippable tour,Skip Tutorial button,Locked overlay until finished,Medium
89,Search,Autocomplete,Web,Help users find results faster,Show predictions as user types,Require full type and enter,Debounced fetch + dropdown,No suggestions,Medium
90,Search,No Results,Web,Dead ends frustrate users,Show 'No results' with suggestions,Blank screen or '0 results',Try searching for X instead,No results found.,Medium
91,Data Entry,Bulk Actions,Web,Editing one by one is tedious,Allow multi-select and bulk edit,Single row actions only,Checkbox column + Action bar,Repeated actions per row,Low
92,AI Interaction,Disclaimer,All,Users need to know they talk to AI,Clearly label AI generated content,Present AI as human,AI Assistant label,Fake human name without label,High
93,AI Interaction,Streaming,All,Waiting for full text is slow,Stream text response token by token,Show loading spinner for 10s+,Typewriter effect,Spinner until 100% complete,Medium
94,Spatial UI,Gaze Hover,VisionOS,Elements should respond to eye tracking before pinch,Scale/highlight element on look,Static element until pinch,hoverEffect(),onTap only,High
95,Spatial UI,Depth Layering,VisionOS,UI needs Z-depth to separate content from environment,Use glass material and z-offset,Flat opaque panels blocking view,.glassBackgroundEffect(),bg-white,Medium
96,Sustainability,Auto-Play Video,Web,Video consumes massive data and energy,Click-to-play or pause when off-screen,Auto-play high-res video loops,playsInline muted preload='none',autoplay loop,Medium
97,Sustainability,Asset Weight,Web,Heavy 3D/Image assets increase carbon footprint,Compress and lazy load 3D models,Load 50MB textures,Draco compression,Raw .obj files,Medium
98,AI Interaction,Feedback Loop,All,AI needs user feedback to improve,Thumps up/down or 'Regenerate',Static output only,Feedback component,Read-only text,Low
99,Accessibility,Motion Sensitivity,All,Parallax/Scroll-jacking causes nausea,Respect prefers-reduced-motion,Force scroll effects,@media (prefers-reduced-motion),ScrollTrigger.create(),High
.claude/skills/ui-ux-pro-max/data/web-interface.csv
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity
1,Accessibility,Icon Button Labels,icon button aria-label,Web,Icon-only buttons must have accessible names,Add aria-label to icon buttons,Icon button without label,"<button aria-label='Close'><XIcon /></button>","<button><XIcon /></button>",Critical
2,Accessibility,Form Control Labels,form input label aria,Web,All form controls need labels or aria-label,Use label element or aria-label,Input without accessible name,"<label for='email'>Email</label><input id='email' />","<input placeholder='Email' />",Critical
3,Accessibility,Keyboard Handlers,keyboard onclick onkeydown,Web,Interactive elements must support keyboard interaction,Add onKeyDown alongside onClick,Click-only interaction,"<div onClick={fn} onKeyDown={fn} tabIndex={0}>","<div onClick={fn}>",High
4,Accessibility,Semantic HTML,semantic button a label,Web,Use semantic HTML before ARIA attributes,Use button/a/label elements,Div with role attribute,"<button onClick={fn}>Submit</button>","<div role='button' onClick={fn}>Submit</div>",High
5,Accessibility,Aria Live,aria-live polite async,Web,Async updates need aria-live for screen readers,Add aria-live='polite' for dynamic content,Silent async updates,"<div aria-live='polite'>{status}</div>","<div>{status}</div> // no announcement",Medium
6,Accessibility,Decorative Icons,aria-hidden decorative icon,Web,Decorative icons should be hidden from screen readers,Add aria-hidden='true' to decorative icons,Decorative icon announced,"<Icon aria-hidden='true' />","<Icon /> // announced as 'image'",Medium
7,Focus,Visible Focus States,focus-visible outline ring,Web,All interactive elements need visible focus states,Use :focus-visible with ring/outline,No focus indication,"focus-visible:ring-2 focus-visible:ring-blue-500","outline-none // no replacement",Critical
8,Focus,Never Remove Outline,outline-none focus replacement,Web,Never remove outline without providing replacement,Replace outline with visible alternative,Remove outline completely,"focus:outline-none focus:ring-2","focus:outline-none // nothing else",Critical
9,Focus,Checkbox Radio Hit Target,checkbox radio label target,Web,Checkbox/radio must share hit target with label,Wrap input and label together,Separate tiny checkbox,"<label class='flex gap-2'><input type='checkbox' /><span>Option</span></label>","<input type='checkbox' id='x' /><label for='x'>Option</label>",Medium
10,Forms,Autocomplete Attribute,autocomplete input form,Web,Inputs need autocomplete attribute for autofill,Add appropriate autocomplete value,Missing autocomplete,"<input autocomplete='email' type='email' />","<input type='email' />",High
11,Forms,Semantic Input Types,input type email tel url,Web,Use semantic input type attributes,Use email/tel/url/number types,text type for everything,"<input type='email' />","<input type='text' /> // for email",Medium
12,Forms,Never Block Paste,paste onpaste password,Web,Never prevent paste functionality,Allow paste on all inputs,Block paste on password/code,"<input type='password' />","<input onPaste={e => e.preventDefault()} />",High
13,Forms,Spellcheck Disable,spellcheck email code,Web,Disable spellcheck on emails and codes,Set spellcheck='false' on codes,Spellcheck on technical input,"<input spellCheck='false' type='email' />","<input type='email' /> // red squiggles",Low
14,Forms,Submit Button Enabled,submit button disabled loading,Web,Keep submit enabled and show spinner during requests,Show loading spinner keep enabled,Disable button during submit,"<button>{loading ? <Spinner /> : 'Submit'}</button>","<button disabled={loading}>Submit</button>",Medium
15,Forms,Inline Errors,error message inline focus,Web,Show error messages inline near the problem field,Inline error with focus on first error,Single error at top,"<input /><span class='text-red-500'>{error}</span>","<div class='error'>{allErrors}</div> // at top",High
16,Performance,Virtualize Lists,virtualize list 50 items,Web,Virtualize lists exceeding 50 items,Use virtual list for large datasets,Render all items,"<VirtualList items={items} />","items.map(item => <Item />)",High
17,Performance,Avoid Layout Reads,layout read render getboundingclientrect,Web,Avoid layout reads during render phase,Read layout in effects or callbacks,getBoundingClientRect in render,"useEffect(() => { el.getBoundingClientRect() })","const rect = el.getBoundingClientRect() // in render",Medium
18,Performance,Batch DOM Operations,batch dom write read,Web,Group DOM operations to minimize reflows,Batch writes then reads,Interleave reads and writes,"writes.forEach(w => w()); reads.forEach(r => r())","write(); read(); write(); read(); // thrashing",Medium
19,Performance,Preconnect CDN,preconnect link cdn,Web,Add preconnect links for CDN domains,Preconnect to known domains,"<link rel='preconnect' href='https://cdn.example.com' />","// no preconnect hint",Low
20,Performance,Lazy Load Images,lazy loading image below-fold,Web,Lazy-load images below the fold,Use loading='lazy' for below-fold images,Load all images eagerly,"<img loading='lazy' src='...' />","<img src='...' /> // above fold only",Medium
21,State,URL Reflects State,url state query params,Web,URL should reflect current UI state,Sync filters/tabs/pagination to URL,State only in memory,"?tab=settings&page=2","useState only // lost on refresh",High
22,State,Deep Linking,deep link stateful component,Web,Stateful components should support deep-linking,Enable sharing current view via URL,No shareable state,"router.push({ query: { ...filters } })","setFilters(f) // not in URL",Medium
23,State,Confirm Destructive Actions,confirm destructive delete modal,Web,Destructive actions require confirmation,Show confirmation dialog before delete,Delete without confirmation,"if (confirm('Delete?')) delete()","onClick={delete} // no confirmation",High
24,Typography,Proper Unicode,unicode ellipsis quotes,Web,Use proper Unicode characters,Use ... curly quotes proper dashes,ASCII approximations,"'Hello...' with proper ellipsis","'Hello...' with three dots",Low
25,Typography,Text Overflow,truncate line-clamp overflow,Web,Handle text overflow properly,Use truncate/line-clamp/break-words,Text overflows container,"<p class='truncate'>Long text...</p>","<p>Long text...</p> // overflows",Medium
26,Typography,Non-Breaking Spaces,nbsp unit brand,Web,Use non-breaking spaces for units and brand names,Use &nbsp; between number and unit,"10&nbsp;kg or Next.js&nbsp;14","10 kg // may wrap",Low
27,Anti-Pattern,No Zoom Disable,viewport zoom disable,Web,Never disable zoom in viewport meta,Allow user zoom,"<meta name='viewport' content='width=device-width'>","<meta name='viewport' content='maximum-scale=1'>",Critical
28,Anti-Pattern,No Transition All,transition all specific,Web,Avoid transition: all - specify properties,Transition specific properties,transition: all,"transition-colors duration-200","transition-all duration-200",Medium
29,Anti-Pattern,Outline Replacement,outline-none ring focus,Web,Never use outline-none without replacement,Provide visible focus replacement,Remove outline with nothing,"focus:outline-none focus:ring-2 focus:ring-blue-500","focus:outline-none // alone",Critical
30,Anti-Pattern,No Hardcoded Dates,date format intl locale,Web,Use Intl for date/number formatting,Use Intl.DateTimeFormat,Hardcoded date format,"new Intl.DateTimeFormat('en').format(date)","date.toLocaleDateString() // or manual format",Medium
.claude/skills/ui-ux-pro-max/scripts/core.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,253 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
UI/UX Pro Max Core - BM25 search engine for UI/UX style guides
"""
import csv
import re
from pathlib import Path
from math import log
from collections import defaultdict
# ============ CONFIGURATION ============
DATA_DIR = Path(__file__).parent.parent / "data"
MAX_RESULTS = 3
CSV_CONFIG = {
    "style": {
        "file": "styles.csv",
        "search_cols": ["Style Category", "Keywords", "Best For", "Type", "AI Prompt Keywords"],
        "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"]
    },
    "color": {
        "file": "colors.csv",
        "search_cols": ["Product Type", "Notes"],
        "output_cols": ["Product Type", "Primary (Hex)", "Secondary (Hex)", "CTA (Hex)", "Background (Hex)", "Text (Hex)", "Notes"]
    },
    "chart": {
        "file": "charts.csv",
        "search_cols": ["Data Type", "Keywords", "Best Chart Type", "Accessibility Notes"],
        "output_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "Color Guidance", "Accessibility Notes", "Library Recommendation", "Interactive Level"]
    },
    "landing": {
        "file": "landing.csv",
        "search_cols": ["Pattern Name", "Keywords", "Conversion Optimization", "Section Order"],
        "output_cols": ["Pattern Name", "Keywords", "Section Order", "Primary CTA Placement", "Color Strategy", "Conversion Optimization"]
    },
    "product": {
        "file": "products.csv",
        "search_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Key Considerations"],
        "output_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Secondary Styles", "Landing Page Pattern", "Dashboard Style (if applicable)", "Color Palette Focus"]
    },
    "ux": {
        "file": "ux-guidelines.csv",
        "search_cols": ["Category", "Issue", "Description", "Platform"],
        "output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
    },
    "typography": {
        "file": "typography.csv",
        "search_cols": ["Font Pairing Name", "Category", "Mood/Style Keywords", "Best For", "Heading Font", "Body Font"],
        "output_cols": ["Font Pairing Name", "Category", "Heading Font", "Body Font", "Mood/Style Keywords", "Best For", "Google Fonts URL", "CSS Import", "Tailwind Config", "Notes"]
    },
    "icons": {
        "file": "icons.csv",
        "search_cols": ["Category", "Icon Name", "Keywords", "Best For"],
        "output_cols": ["Category", "Icon Name", "Keywords", "Library", "Import Code", "Usage", "Best For", "Style"]
    },
    "react": {
        "file": "react-performance.csv",
        "search_cols": ["Category", "Issue", "Keywords", "Description"],
        "output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
    },
    "web": {
        "file": "web-interface.csv",
        "search_cols": ["Category", "Issue", "Keywords", "Description"],
        "output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
    }
}
STACK_CONFIG = {
    "html-tailwind": {"file": "stacks/html-tailwind.csv"},
    "react": {"file": "stacks/react.csv"},
    "nextjs": {"file": "stacks/nextjs.csv"},
    "astro": {"file": "stacks/astro.csv"},
    "vue": {"file": "stacks/vue.csv"},
    "nuxtjs": {"file": "stacks/nuxtjs.csv"},
    "nuxt-ui": {"file": "stacks/nuxt-ui.csv"},
    "svelte": {"file": "stacks/svelte.csv"},
    "swiftui": {"file": "stacks/swiftui.csv"},
    "react-native": {"file": "stacks/react-native.csv"},
    "flutter": {"file": "stacks/flutter.csv"},
    "shadcn": {"file": "stacks/shadcn.csv"},
    "jetpack-compose": {"file": "stacks/jetpack-compose.csv"}
}
# Common columns for all stacks
_STACK_COLS = {
    "search_cols": ["Category", "Guideline", "Description", "Do", "Don't"],
    "output_cols": ["Category", "Guideline", "Description", "Do", "Don't", "Code Good", "Code Bad", "Severity", "Docs URL"]
}
AVAILABLE_STACKS = list(STACK_CONFIG.keys())
# ============ BM25 IMPLEMENTATION ============
class BM25:
    """BM25 ranking algorithm for text search"""
    def __init__(self, k1=1.5, b=0.75):
        self.k1 = k1
        self.b = b
        self.corpus = []
        self.doc_lengths = []
        self.avgdl = 0
        self.idf = {}
        self.doc_freqs = defaultdict(int)
        self.N = 0
    def tokenize(self, text):
        """Lowercase, split, remove punctuation, filter short words"""
        text = re.sub(r'[^\w\s]', ' ', str(text).lower())
        return [w for w in text.split() if len(w) > 2]
    def fit(self, documents):
        """Build BM25 index from documents"""
        self.corpus = [self.tokenize(doc) for doc in documents]
        self.N = len(self.corpus)
        if self.N == 0:
            return
        self.doc_lengths = [len(doc) for doc in self.corpus]
        self.avgdl = sum(self.doc_lengths) / self.N
        for doc in self.corpus:
            seen = set()
            for word in doc:
                if word not in seen:
                    self.doc_freqs[word] += 1
                    seen.add(word)
        for word, freq in self.doc_freqs.items():
            self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
    def score(self, query):
        """Score all documents against query"""
        query_tokens = self.tokenize(query)
        scores = []
        for idx, doc in enumerate(self.corpus):
            score = 0
            doc_len = self.doc_lengths[idx]
            term_freqs = defaultdict(int)
            for word in doc:
                term_freqs[word] += 1
            for token in query_tokens:
                if token in self.idf:
                    tf = term_freqs[token]
                    idf = self.idf[token]
                    numerator = tf * (self.k1 + 1)
                    denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
                    score += idf * numerator / denominator
            scores.append((idx, score))
        return sorted(scores, key=lambda x: x[1], reverse=True)
# ============ SEARCH FUNCTIONS ============
def _load_csv(filepath):
    """Load CSV and return list of dicts"""
    with open(filepath, 'r', encoding='utf-8') as f:
        return list(csv.DictReader(f))
def _search_csv(filepath, search_cols, output_cols, query, max_results):
    """Core search function using BM25"""
    if not filepath.exists():
        return []
    data = _load_csv(filepath)
    # Build documents from search columns
    documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
    # BM25 search
    bm25 = BM25()
    bm25.fit(documents)
    ranked = bm25.score(query)
    # Get top results with score > 0
    results = []
    for idx, score in ranked[:max_results]:
        if score > 0:
            row = data[idx]
            results.append({col: row.get(col, "") for col in output_cols if col in row})
    return results
def detect_domain(query):
    """Auto-detect the most relevant domain from query"""
    query_lower = query.lower()
    domain_keywords = {
        "color": ["color", "palette", "hex", "#", "rgb"],
        "chart": ["chart", "graph", "visualization", "trend", "bar", "pie", "scatter", "heatmap", "funnel"],
        "landing": ["landing", "page", "cta", "conversion", "hero", "testimonial", "pricing", "section"],
        "product": ["saas", "ecommerce", "e-commerce", "fintech", "healthcare", "gaming", "portfolio", "crypto", "dashboard"],
        "style": ["style", "design", "ui", "minimalism", "glassmorphism", "neumorphism", "brutalism", "dark mode", "flat", "aurora", "prompt", "css", "implementation", "variable", "checklist", "tailwind"],
        "ux": ["ux", "usability", "accessibility", "wcag", "touch", "scroll", "animation", "keyboard", "navigation", "mobile"],
        "typography": ["font", "typography", "heading", "serif", "sans"],
        "icons": ["icon", "icons", "lucide", "heroicons", "symbol", "glyph", "pictogram", "svg icon"],
        "react": ["react", "next.js", "nextjs", "suspense", "memo", "usecallback", "useeffect", "rerender", "bundle", "waterfall", "barrel", "dynamic import", "rsc", "server component"],
        "web": ["aria", "focus", "outline", "semantic", "virtualize", "autocomplete", "form", "input type", "preconnect"]
    }
    scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
    best = max(scores, key=scores.get)
    return best if scores[best] > 0 else "style"
def search(query, domain=None, max_results=MAX_RESULTS):
    """Main search function with auto-domain detection"""
    if domain is None:
        domain = detect_domain(query)
    config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
    filepath = DATA_DIR / config["file"]
    if not filepath.exists():
        return {"error": f"File not found: {filepath}", "domain": domain}
    results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
    return {
        "domain": domain,
        "query": query,
        "file": config["file"],
        "count": len(results),
        "results": results
    }
def search_stack(query, stack, max_results=MAX_RESULTS):
    """Search stack-specific guidelines"""
    if stack not in STACK_CONFIG:
        return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
    filepath = DATA_DIR / STACK_CONFIG[stack]["file"]
    if not filepath.exists():
        return {"error": f"Stack file not found: {filepath}", "stack": stack}
    results = _search_csv(filepath, _STACK_COLS["search_cols"], _STACK_COLS["output_cols"], query, max_results)
    return {
        "domain": "stack",
        "stack": stack,
        "query": query,
        "file": STACK_CONFIG[stack]["file"],
        "count": len(results),
        "results": results
    }
.claude/skills/ui-ux-pro-max/scripts/design_system.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1067 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Design System Generator - Aggregates search results and applies reasoning
to generate comprehensive design system recommendations.
Usage:
    from design_system import generate_design_system
    result = generate_design_system("SaaS dashboard", "My Project")
    # With persistence (Master + Overrides pattern)
    result = generate_design_system("SaaS dashboard", "My Project", persist=True)
    result = generate_design_system("SaaS dashboard", "My Project", persist=True, page="dashboard")
"""
import csv
import json
import os
from datetime import datetime
from pathlib import Path
from core import search, DATA_DIR
# ============ CONFIGURATION ============
REASONING_FILE = "ui-reasoning.csv"
SEARCH_CONFIG = {
    "product": {"max_results": 1},
    "style": {"max_results": 3},
    "color": {"max_results": 2},
    "landing": {"max_results": 2},
    "typography": {"max_results": 2}
}
# ============ DESIGN SYSTEM GENERATOR ============
class DesignSystemGenerator:
    """Generates design system recommendations from aggregated searches."""
    def __init__(self):
        self.reasoning_data = self._load_reasoning()
    def _load_reasoning(self) -> list:
        """Load reasoning rules from CSV."""
        filepath = DATA_DIR / REASONING_FILE
        if not filepath.exists():
            return []
        with open(filepath, 'r', encoding='utf-8') as f:
            return list(csv.DictReader(f))
    def _multi_domain_search(self, query: str, style_priority: list = None) -> dict:
        """Execute searches across multiple domains."""
        results = {}
        for domain, config in SEARCH_CONFIG.items():
            if domain == "style" and style_priority:
                # For style, also search with priority keywords
                priority_query = " ".join(style_priority[:2]) if style_priority else query
                combined_query = f"{query} {priority_query}"
                results[domain] = search(combined_query, domain, config["max_results"])
            else:
                results[domain] = search(query, domain, config["max_results"])
        return results
    def _find_reasoning_rule(self, category: str) -> dict:
        """Find matching reasoning rule for a category."""
        category_lower = category.lower()
        # Try exact match first
        for rule in self.reasoning_data:
            if rule.get("UI_Category", "").lower() == category_lower:
                return rule
        # Try partial match
        for rule in self.reasoning_data:
            ui_cat = rule.get("UI_Category", "").lower()
            if ui_cat in category_lower or category_lower in ui_cat:
                return rule
        # Try keyword match
        for rule in self.reasoning_data:
            ui_cat = rule.get("UI_Category", "").lower()
            keywords = ui_cat.replace("/", " ").replace("-", " ").split()
            if any(kw in category_lower for kw in keywords):
                return rule
        return {}
    def _apply_reasoning(self, category: str, search_results: dict) -> dict:
        """Apply reasoning rules to search results."""
        rule = self._find_reasoning_rule(category)
        if not rule:
            return {
                "pattern": "Hero + Features + CTA",
                "style_priority": ["Minimalism", "Flat Design"],
                "color_mood": "Professional",
                "typography_mood": "Clean",
                "key_effects": "Subtle hover transitions",
                "anti_patterns": "",
                "decision_rules": {},
                "severity": "MEDIUM"
            }
        # Parse decision rules JSON
        decision_rules = {}
        try:
            decision_rules = json.loads(rule.get("Decision_Rules", "{}"))
        except json.JSONDecodeError:
            pass
        return {
            "pattern": rule.get("Recommended_Pattern", ""),
            "style_priority": [s.strip() for s in rule.get("Style_Priority", "").split("+")],
            "color_mood": rule.get("Color_Mood", ""),
            "typography_mood": rule.get("Typography_Mood", ""),
            "key_effects": rule.get("Key_Effects", ""),
            "anti_patterns": rule.get("Anti_Patterns", ""),
            "decision_rules": decision_rules,
            "severity": rule.get("Severity", "MEDIUM")
        }
    def _select_best_match(self, results: list, priority_keywords: list) -> dict:
        """Select best matching result based on priority keywords."""
        if not results:
            return {}
        if not priority_keywords:
            return results[0]
        # First: try exact style name match
        for priority in priority_keywords:
            priority_lower = priority.lower().strip()
            for result in results:
                style_name = result.get("Style Category", "").lower()
                if priority_lower in style_name or style_name in priority_lower:
                    return result
        # Second: score by keyword match in all fields
        scored = []
        for result in results:
            result_str = str(result).lower()
            score = 0
            for kw in priority_keywords:
                kw_lower = kw.lower().strip()
                # Higher score for style name match
                if kw_lower in result.get("Style Category", "").lower():
                    score += 10
                # Lower score for keyword field match
                elif kw_lower in result.get("Keywords", "").lower():
                    score += 3
                # Even lower for other field matches
                elif kw_lower in result_str:
                    score += 1
            scored.append((score, result))
        scored.sort(key=lambda x: x[0], reverse=True)
        return scored[0][1] if scored and scored[0][0] > 0 else results[0]
    def _extract_results(self, search_result: dict) -> list:
        """Extract results list from search result dict."""
        return search_result.get("results", [])
    def generate(self, query: str, project_name: str = None) -> dict:
        """Generate complete design system recommendation."""
        # Step 1: First search product to get category
        product_result = search(query, "product", 1)
        product_results = product_result.get("results", [])
        category = "General"
        if product_results:
            category = product_results[0].get("Product Type", "General")
        # Step 2: Get reasoning rules for this category
        reasoning = self._apply_reasoning(category, {})
        style_priority = reasoning.get("style_priority", [])
        # Step 3: Multi-domain search with style priority hints
        search_results = self._multi_domain_search(query, style_priority)
        search_results["product"] = product_result  # Reuse product search
        # Step 4: Select best matches from each domain using priority
        style_results = self._extract_results(search_results.get("style", {}))
        color_results = self._extract_results(search_results.get("color", {}))
        typography_results = self._extract_results(search_results.get("typography", {}))
        landing_results = self._extract_results(search_results.get("landing", {}))
        best_style = self._select_best_match(style_results, reasoning.get("style_priority", []))
        best_color = color_results[0] if color_results else {}
        best_typography = typography_results[0] if typography_results else {}
        best_landing = landing_results[0] if landing_results else {}
        # Step 5: Build final recommendation
        # Combine effects from both reasoning and style search
        style_effects = best_style.get("Effects & Animation", "")
        reasoning_effects = reasoning.get("key_effects", "")
        combined_effects = style_effects if style_effects else reasoning_effects
        return {
            "project_name": project_name or query.upper(),
            "category": category,
            "pattern": {
                "name": best_landing.get("Pattern Name", reasoning.get("pattern", "Hero + Features + CTA")),
                "sections": best_landing.get("Section Order", "Hero > Features > CTA"),
                "cta_placement": best_landing.get("Primary CTA Placement", "Above fold"),
                "color_strategy": best_landing.get("Color Strategy", ""),
                "conversion": best_landing.get("Conversion Optimization", "")
            },
            "style": {
                "name": best_style.get("Style Category", "Minimalism"),
                "type": best_style.get("Type", "General"),
                "effects": style_effects,
                "keywords": best_style.get("Keywords", ""),
                "best_for": best_style.get("Best For", ""),
                "performance": best_style.get("Performance", ""),
                "accessibility": best_style.get("Accessibility", "")
            },
            "colors": {
                "primary": best_color.get("Primary (Hex)", "#2563EB"),
                "secondary": best_color.get("Secondary (Hex)", "#3B82F6"),
                "cta": best_color.get("CTA (Hex)", "#F97316"),
                "background": best_color.get("Background (Hex)", "#F8FAFC"),
                "text": best_color.get("Text (Hex)", "#1E293B"),
                "notes": best_color.get("Notes", "")
            },
            "typography": {
                "heading": best_typography.get("Heading Font", "Inter"),
                "body": best_typography.get("Body Font", "Inter"),
                "mood": best_typography.get("Mood/Style Keywords", reasoning.get("typography_mood", "")),
                "best_for": best_typography.get("Best For", ""),
                "google_fonts_url": best_typography.get("Google Fonts URL", ""),
                "css_import": best_typography.get("CSS Import", "")
            },
            "key_effects": combined_effects,
            "anti_patterns": reasoning.get("anti_patterns", ""),
            "decision_rules": reasoning.get("decision_rules", {}),
            "severity": reasoning.get("severity", "MEDIUM")
        }
# ============ OUTPUT FORMATTERS ============
BOX_WIDTH = 90  # Wider box for more content
def format_ascii_box(design_system: dict) -> str:
    """Format design system as ASCII box with emojis (MCP-style)."""
    project = design_system.get("project_name", "PROJECT")
    pattern = design_system.get("pattern", {})
    style = design_system.get("style", {})
    colors = design_system.get("colors", {})
    typography = design_system.get("typography", {})
    effects = design_system.get("key_effects", "")
    anti_patterns = design_system.get("anti_patterns", "")
    def wrap_text(text: str, prefix: str, width: int) -> list:
        """Wrap long text into multiple lines."""
        if not text:
            return []
        words = text.split()
        lines = []
        current_line = prefix
        for word in words:
            if len(current_line) + len(word) + 1 <= width - 2:
                current_line += (" " if current_line != prefix else "") + word
            else:
                if current_line != prefix:
                    lines.append(current_line)
                current_line = prefix + word
        if current_line != prefix:
            lines.append(current_line)
        return lines
    # Build sections from pattern
    sections = pattern.get("sections", "").split(">")
    sections = [s.strip() for s in sections if s.strip()]
    # Build output lines
    lines = []
    w = BOX_WIDTH - 1
    lines.append("+" + "-" * w + "+")
    lines.append(f"|  TARGET: {project} - RECOMMENDED DESIGN SYSTEM".ljust(BOX_WIDTH) + "|")
    lines.append("+" + "-" * w + "+")
    lines.append("|" + " " * BOX_WIDTH + "|")
    # Pattern section
    lines.append(f"|  PATTERN: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|")
    if pattern.get('conversion'):
        lines.append(f"|     Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "|")
    if pattern.get('cta_placement'):
        lines.append(f"|     CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|")
    lines.append("|     Sections:".ljust(BOX_WIDTH) + "|")
    for i, section in enumerate(sections, 1):
        lines.append(f"|       {i}. {section}".ljust(BOX_WIDTH) + "|")
    lines.append("|" + " " * BOX_WIDTH + "|")
    # Style section
    lines.append(f"|  STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|")
    if style.get("keywords"):
        for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
    if style.get("best_for"):
        for line in wrap_text(f"Best For: {style.get('best_for', '')}", "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
    if style.get("performance") or style.get("accessibility"):
        perf_a11y = f"Performance: {style.get('performance', '')} | Accessibility: {style.get('accessibility', '')}"
        lines.append(f"|     {perf_a11y}".ljust(BOX_WIDTH) + "|")
    lines.append("|" + " " * BOX_WIDTH + "|")
    # Colors section
    lines.append("|  COLORS:".ljust(BOX_WIDTH) + "|")
    lines.append(f"|     Primary:    {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|")
    lines.append(f"|     Secondary:  {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|")
    lines.append(f"|     CTA:        {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|")
    lines.append(f"|     Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|")
    lines.append(f"|     Text:       {colors.get('text', '')}".ljust(BOX_WIDTH) + "|")
    if colors.get("notes"):
        for line in wrap_text(f"Notes: {colors.get('notes', '')}", "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
    lines.append("|" + " " * BOX_WIDTH + "|")
    # Typography section
    lines.append(f"|  TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|")
    if typography.get("mood"):
        for line in wrap_text(f"Mood: {typography.get('mood', '')}", "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
    if typography.get("best_for"):
        for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
    if typography.get("google_fonts_url"):
        lines.append(f"|     Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "|")
    if typography.get("css_import"):
        lines.append(f"|     CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "|")
    lines.append("|" + " " * BOX_WIDTH + "|")
    # Key Effects section
    if effects:
        lines.append("|  KEY EFFECTS:".ljust(BOX_WIDTH) + "|")
        for line in wrap_text(effects, "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
        lines.append("|" + " " * BOX_WIDTH + "|")
    # Anti-patterns section
    if anti_patterns:
        lines.append("|  AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|")
        for line in wrap_text(anti_patterns, "|     ", BOX_WIDTH):
            lines.append(line.ljust(BOX_WIDTH) + "|")
        lines.append("|" + " " * BOX_WIDTH + "|")
    # Pre-Delivery Checklist section
    lines.append("|  PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|")
    checklist_items = [
        "[ ] No emojis as icons (use SVG: Heroicons/Lucide)",
        "[ ] cursor-pointer on all clickable elements",
        "[ ] Hover states with smooth transitions (150-300ms)",
        "[ ] Light mode: text contrast 4.5:1 minimum",
        "[ ] Focus states visible for keyboard nav",
        "[ ] prefers-reduced-motion respected",
        "[ ] Responsive: 375px, 768px, 1024px, 1440px"
    ]
    for item in checklist_items:
        lines.append(f"|     {item}".ljust(BOX_WIDTH) + "|")
    lines.append("|" + " " * BOX_WIDTH + "|")
    lines.append("+" + "-" * w + "+")
    return "\n".join(lines)
def format_markdown(design_system: dict) -> str:
    """Format design system as markdown."""
    project = design_system.get("project_name", "PROJECT")
    pattern = design_system.get("pattern", {})
    style = design_system.get("style", {})
    colors = design_system.get("colors", {})
    typography = design_system.get("typography", {})
    effects = design_system.get("key_effects", "")
    anti_patterns = design_system.get("anti_patterns", "")
    lines = []
    lines.append(f"## Design System: {project}")
    lines.append("")
    # Pattern section
    lines.append("### Pattern")
    lines.append(f"- **Name:** {pattern.get('name', '')}")
    if pattern.get('conversion'):
        lines.append(f"- **Conversion Focus:** {pattern.get('conversion', '')}")
    if pattern.get('cta_placement'):
        lines.append(f"- **CTA Placement:** {pattern.get('cta_placement', '')}")
    if pattern.get('color_strategy'):
        lines.append(f"- **Color Strategy:** {pattern.get('color_strategy', '')}")
    lines.append(f"- **Sections:** {pattern.get('sections', '')}")
    lines.append("")
    # Style section
    lines.append("### Style")
    lines.append(f"- **Name:** {style.get('name', '')}")
    if style.get('keywords'):
        lines.append(f"- **Keywords:** {style.get('keywords', '')}")
    if style.get('best_for'):
        lines.append(f"- **Best For:** {style.get('best_for', '')}")
    if style.get('performance') or style.get('accessibility'):
        lines.append(f"- **Performance:** {style.get('performance', '')} | **Accessibility:** {style.get('accessibility', '')}")
    lines.append("")
    # Colors section
    lines.append("### Colors")
    lines.append(f"| Role | Hex |")
    lines.append(f"|------|-----|")
    lines.append(f"| Primary | {colors.get('primary', '')} |")
    lines.append(f"| Secondary | {colors.get('secondary', '')} |")
    lines.append(f"| CTA | {colors.get('cta', '')} |")
    lines.append(f"| Background | {colors.get('background', '')} |")
    lines.append(f"| Text | {colors.get('text', '')} |")
    if colors.get("notes"):
        lines.append(f"\n*Notes: {colors.get('notes', '')}*")
    lines.append("")
    # Typography section
    lines.append("### Typography")
    lines.append(f"- **Heading:** {typography.get('heading', '')}")
    lines.append(f"- **Body:** {typography.get('body', '')}")
    if typography.get("mood"):
        lines.append(f"- **Mood:** {typography.get('mood', '')}")
    if typography.get("best_for"):
        lines.append(f"- **Best For:** {typography.get('best_for', '')}")
    if typography.get("google_fonts_url"):
        lines.append(f"- **Google Fonts:** {typography.get('google_fonts_url', '')}")
    if typography.get("css_import"):
        lines.append(f"- **CSS Import:**")
        lines.append(f"```css")
        lines.append(f"{typography.get('css_import', '')}")
        lines.append(f"```")
    lines.append("")
    # Key Effects section
    if effects:
        lines.append("### Key Effects")
        lines.append(f"{effects}")
        lines.append("")
    # Anti-patterns section
    if anti_patterns:
        lines.append("### Avoid (Anti-patterns)")
        newline_bullet = '\n- '
        lines.append(f"- {anti_patterns.replace(' + ', newline_bullet)}")
        lines.append("")
    # Pre-Delivery Checklist section
    lines.append("### Pre-Delivery Checklist")
    lines.append("- [ ] No emojis as icons (use SVG: Heroicons/Lucide)")
    lines.append("- [ ] cursor-pointer on all clickable elements")
    lines.append("- [ ] Hover states with smooth transitions (150-300ms)")
    lines.append("- [ ] Light mode: text contrast 4.5:1 minimum")
    lines.append("- [ ] Focus states visible for keyboard nav")
    lines.append("- [ ] prefers-reduced-motion respected")
    lines.append("- [ ] Responsive: 375px, 768px, 1024px, 1440px")
    lines.append("")
    return "\n".join(lines)
# ============ MAIN ENTRY POINT ============
def generate_design_system(query: str, project_name: str = None, output_format: str = "ascii",
                           persist: bool = False, page: str = None, output_dir: str = None) -> str:
    """
    Main entry point for design system generation.
    Args:
        query: Search query (e.g., "SaaS dashboard", "e-commerce luxury")
        project_name: Optional project name for output header
        output_format: "ascii" (default) or "markdown"
        persist: If True, save design system to design-system/ folder
        page: Optional page name for page-specific override file
        output_dir: Optional output directory (defaults to current working directory)
    Returns:
        Formatted design system string
    """
    generator = DesignSystemGenerator()
    design_system = generator.generate(query, project_name)
    # Persist to files if requested
    if persist:
        persist_design_system(design_system, page, output_dir, query)
    if output_format == "markdown":
        return format_markdown(design_system)
    return format_ascii_box(design_system)
# ============ PERSISTENCE FUNCTIONS ============
def persist_design_system(design_system: dict, page: str = None, output_dir: str = None, page_query: str = None) -> dict:
    """
    Persist design system to design-system/<project>/ folder using Master + Overrides pattern.
    Args:
        design_system: The generated design system dictionary
        page: Optional page name for page-specific override file
        output_dir: Optional output directory (defaults to current working directory)
        page_query: Optional query string for intelligent page override generation
    Returns:
        dict with created file paths and status
    """
    base_dir = Path(output_dir) if output_dir else Path.cwd()
    # Use project name for project-specific folder
    project_name = design_system.get("project_name", "default")
    project_slug = project_name.lower().replace(' ', '-')
    design_system_dir = base_dir / "design-system" / project_slug
    pages_dir = design_system_dir / "pages"
    created_files = []
    # Create directories
    design_system_dir.mkdir(parents=True, exist_ok=True)
    pages_dir.mkdir(parents=True, exist_ok=True)
    master_file = design_system_dir / "MASTER.md"
    # Generate and write MASTER.md
    master_content = format_master_md(design_system)
    with open(master_file, 'w', encoding='utf-8') as f:
        f.write(master_content)
    created_files.append(str(master_file))
    # If page is specified, create page override file with intelligent content
    if page:
        page_file = pages_dir / f"{page.lower().replace(' ', '-')}.md"
        page_content = format_page_override_md(design_system, page, page_query)
        with open(page_file, 'w', encoding='utf-8') as f:
            f.write(page_content)
        created_files.append(str(page_file))
    return {
        "status": "success",
        "design_system_dir": str(design_system_dir),
        "created_files": created_files
    }
def format_master_md(design_system: dict) -> str:
    """Format design system as MASTER.md with hierarchical override logic."""
    project = design_system.get("project_name", "PROJECT")
    pattern = design_system.get("pattern", {})
    style = design_system.get("style", {})
    colors = design_system.get("colors", {})
    typography = design_system.get("typography", {})
    effects = design_system.get("key_effects", "")
    anti_patterns = design_system.get("anti_patterns", "")
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    lines = []
    # Logic header
    lines.append("# Design System Master File")
    lines.append("")
    lines.append("> **LOGIC:** When building a specific page, first check `design-system/pages/[page-name].md`.")
    lines.append("> If that file exists, its rules **override** this Master file.")
    lines.append("> If not, strictly follow the rules below.")
    lines.append("")
    lines.append("---")
    lines.append("")
    lines.append(f"**Project:** {project}")
    lines.append(f"**Generated:** {timestamp}")
    lines.append(f"**Category:** {design_system.get('category', 'General')}")
    lines.append("")
    lines.append("---")
    lines.append("")
    # Global Rules section
    lines.append("## Global Rules")
    lines.append("")
    # Color Palette
    lines.append("### Color Palette")
    lines.append("")
    lines.append("| Role | Hex | CSS Variable |")
    lines.append("|------|-----|--------------|")
    lines.append(f"| Primary | `{colors.get('primary', '#2563EB')}` | `--color-primary` |")
    lines.append(f"| Secondary | `{colors.get('secondary', '#3B82F6')}` | `--color-secondary` |")
    lines.append(f"| CTA/Accent | `{colors.get('cta', '#F97316')}` | `--color-cta` |")
    lines.append(f"| Background | `{colors.get('background', '#F8FAFC')}` | `--color-background` |")
    lines.append(f"| Text | `{colors.get('text', '#1E293B')}` | `--color-text` |")
    lines.append("")
    if colors.get("notes"):
        lines.append(f"**Color Notes:** {colors.get('notes', '')}")
        lines.append("")
    # Typography
    lines.append("### Typography")
    lines.append("")
    lines.append(f"- **Heading Font:** {typography.get('heading', 'Inter')}")
    lines.append(f"- **Body Font:** {typography.get('body', 'Inter')}")
    if typography.get("mood"):
        lines.append(f"- **Mood:** {typography.get('mood', '')}")
    if typography.get("google_fonts_url"):
        lines.append(f"- **Google Fonts:** [{typography.get('heading', '')} + {typography.get('body', '')}]({typography.get('google_fonts_url', '')})")
    lines.append("")
    if typography.get("css_import"):
        lines.append("**CSS Import:**")
        lines.append("```css")
        lines.append(typography.get("css_import", ""))
        lines.append("```")
        lines.append("")
    # Spacing Variables
    lines.append("### Spacing Variables")
    lines.append("")
    lines.append("| Token | Value | Usage |")
    lines.append("|-------|-------|-------|")
    lines.append("| `--space-xs` | `4px` / `0.25rem` | Tight gaps |")
    lines.append("| `--space-sm` | `8px` / `0.5rem` | Icon gaps, inline spacing |")
    lines.append("| `--space-md` | `16px` / `1rem` | Standard padding |")
    lines.append("| `--space-lg` | `24px` / `1.5rem` | Section padding |")
    lines.append("| `--space-xl` | `32px` / `2rem` | Large gaps |")
    lines.append("| `--space-2xl` | `48px` / `3rem` | Section margins |")
    lines.append("| `--space-3xl` | `64px` / `4rem` | Hero padding |")
    lines.append("")
    # Shadow Depths
    lines.append("### Shadow Depths")
    lines.append("")
    lines.append("| Level | Value | Usage |")
    lines.append("|-------|-------|-------|")
    lines.append("| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle lift |")
    lines.append("| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons |")
    lines.append("| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns |")
    lines.append("| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards |")
    lines.append("")
    # Component Specs section
    lines.append("---")
    lines.append("")
    lines.append("## Component Specs")
    lines.append("")
    # Buttons
    lines.append("### Buttons")
    lines.append("")
    lines.append("```css")
    lines.append("/* Primary Button */")
    lines.append(".btn-primary {")
    lines.append(f"  background: {colors.get('cta', '#F97316')};")
    lines.append("  color: white;")
    lines.append("  padding: 12px 24px;")
    lines.append("  border-radius: 8px;")
    lines.append("  font-weight: 600;")
    lines.append("  transition: all 200ms ease;")
    lines.append("  cursor: pointer;")
    lines.append("}")
    lines.append("")
    lines.append(".btn-primary:hover {")
    lines.append("  opacity: 0.9;")
    lines.append("  transform: translateY(-1px);")
    lines.append("}")
    lines.append("")
    lines.append("/* Secondary Button */")
    lines.append(".btn-secondary {")
    lines.append(f"  background: transparent;")
    lines.append(f"  color: {colors.get('primary', '#2563EB')};")
    lines.append(f"  border: 2px solid {colors.get('primary', '#2563EB')};")
    lines.append("  padding: 12px 24px;")
    lines.append("  border-radius: 8px;")
    lines.append("  font-weight: 600;")
    lines.append("  transition: all 200ms ease;")
    lines.append("  cursor: pointer;")
    lines.append("}")
    lines.append("```")
    lines.append("")
    # Cards
    lines.append("### Cards")
    lines.append("")
    lines.append("```css")
    lines.append(".card {")
    lines.append(f"  background: {colors.get('background', '#FFFFFF')};")
    lines.append("  border-radius: 12px;")
    lines.append("  padding: 24px;")
    lines.append("  box-shadow: var(--shadow-md);")
    lines.append("  transition: all 200ms ease;")
    lines.append("  cursor: pointer;")
    lines.append("}")
    lines.append("")
    lines.append(".card:hover {")
    lines.append("  box-shadow: var(--shadow-lg);")
    lines.append("  transform: translateY(-2px);")
    lines.append("}")
    lines.append("```")
    lines.append("")
    # Inputs
    lines.append("### Inputs")
    lines.append("")
    lines.append("```css")
    lines.append(".input {")
    lines.append("  padding: 12px 16px;")
    lines.append("  border: 1px solid #E2E8F0;")
    lines.append("  border-radius: 8px;")
    lines.append("  font-size: 16px;")
    lines.append("  transition: border-color 200ms ease;")
    lines.append("}")
    lines.append("")
    lines.append(".input:focus {")
    lines.append(f"  border-color: {colors.get('primary', '#2563EB')};")
    lines.append("  outline: none;")
    lines.append(f"  box-shadow: 0 0 0 3px {colors.get('primary', '#2563EB')}20;")
    lines.append("}")
    lines.append("```")
    lines.append("")
    # Modals
    lines.append("### Modals")
    lines.append("")
    lines.append("```css")
    lines.append(".modal-overlay {")
    lines.append("  background: rgba(0, 0, 0, 0.5);")
    lines.append("  backdrop-filter: blur(4px);")
    lines.append("}")
    lines.append("")
    lines.append(".modal {")
    lines.append("  background: white;")
    lines.append("  border-radius: 16px;")
    lines.append("  padding: 32px;")
    lines.append("  box-shadow: var(--shadow-xl);")
    lines.append("  max-width: 500px;")
    lines.append("  width: 90%;")
    lines.append("}")
    lines.append("```")
    lines.append("")
    # Style section
    lines.append("---")
    lines.append("")
    lines.append("## Style Guidelines")
    lines.append("")
    lines.append(f"**Style:** {style.get('name', 'Minimalism')}")
    lines.append("")
    if style.get("keywords"):
        lines.append(f"**Keywords:** {style.get('keywords', '')}")
        lines.append("")
    if style.get("best_for"):
        lines.append(f"**Best For:** {style.get('best_for', '')}")
        lines.append("")
    if effects:
        lines.append(f"**Key Effects:** {effects}")
        lines.append("")
    # Layout Pattern
    lines.append("### Page Pattern")
    lines.append("")
    lines.append(f"**Pattern Name:** {pattern.get('name', '')}")
    lines.append("")
    if pattern.get('conversion'):
        lines.append(f"- **Conversion Strategy:** {pattern.get('conversion', '')}")
    if pattern.get('cta_placement'):
        lines.append(f"- **CTA Placement:** {pattern.get('cta_placement', '')}")
    lines.append(f"- **Section Order:** {pattern.get('sections', '')}")
    lines.append("")
    # Anti-Patterns section
    lines.append("---")
    lines.append("")
    lines.append("## Anti-Patterns (Do NOT Use)")
    lines.append("")
    if anti_patterns:
        anti_list = [a.strip() for a in anti_patterns.split("+")]
        for anti in anti_list:
            if anti:
                lines.append(f"- âŒ {anti}")
    lines.append("")
    lines.append("### Additional Forbidden Patterns")
    lines.append("")
    lines.append("- âŒ **Emojis as icons** â€” Use SVG icons (Heroicons, Lucide, Simple Icons)")
    lines.append("- âŒ **Missing cursor:pointer** â€” All clickable elements must have cursor:pointer")
    lines.append("- âŒ **Layout-shifting hovers** â€” Avoid scale transforms that shift layout")
    lines.append("- âŒ **Low contrast text** â€” Maintain 4.5:1 minimum contrast ratio")
    lines.append("- âŒ **Instant state changes** â€” Always use transitions (150-300ms)")
    lines.append("- âŒ **Invisible focus states** â€” Focus states must be visible for a11y")
    lines.append("")
    # Pre-Delivery Checklist
    lines.append("---")
    lines.append("")
    lines.append("## Pre-Delivery Checklist")
    lines.append("")
    lines.append("Before delivering any UI code, verify:")
    lines.append("")
    lines.append("- [ ] No emojis used as icons (use SVG instead)")
    lines.append("- [ ] All icons from consistent icon set (Heroicons/Lucide)")
    lines.append("- [ ] `cursor-pointer` on all clickable elements")
    lines.append("- [ ] Hover states with smooth transitions (150-300ms)")
    lines.append("- [ ] Light mode: text contrast 4.5:1 minimum")
    lines.append("- [ ] Focus states visible for keyboard navigation")
    lines.append("- [ ] `prefers-reduced-motion` respected")
    lines.append("- [ ] Responsive: 375px, 768px, 1024px, 1440px")
    lines.append("- [ ] No content hidden behind fixed navbars")
    lines.append("- [ ] No horizontal scroll on mobile")
    lines.append("")
    return "\n".join(lines)
def format_page_override_md(design_system: dict, page_name: str, page_query: str = None) -> str:
    """Format a page-specific override file with intelligent AI-generated content."""
    project = design_system.get("project_name", "PROJECT")
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    page_title = page_name.replace("-", " ").replace("_", " ").title()
    # Detect page type and generate intelligent overrides
    page_overrides = _generate_intelligent_overrides(page_name, page_query, design_system)
    lines = []
    lines.append(f"# {page_title} Page Overrides")
    lines.append("")
    lines.append(f"> **PROJECT:** {project}")
    lines.append(f"> **Generated:** {timestamp}")
    lines.append(f"> **Page Type:** {page_overrides.get('page_type', 'General')}")
    lines.append("")
    lines.append("> âš ï¸ **IMPORTANT:** Rules in this file **override** the Master file (`design-system/MASTER.md`).")
    lines.append("> Only deviations from the Master are documented here. For all other rules, refer to the Master.")
    lines.append("")
    lines.append("---")
    lines.append("")
    # Page-specific rules with actual content
    lines.append("## Page-Specific Rules")
    lines.append("")
    # Layout Overrides
    lines.append("### Layout Overrides")
    lines.append("")
    layout = page_overrides.get("layout", {})
    if layout:
        for key, value in layout.items():
            lines.append(f"- **{key}:** {value}")
    else:
        lines.append("- No overrides â€” use Master layout")
    lines.append("")
    # Spacing Overrides
    lines.append("### Spacing Overrides")
    lines.append("")
    spacing = page_overrides.get("spacing", {})
    if spacing:
        for key, value in spacing.items():
            lines.append(f"- **{key}:** {value}")
    else:
        lines.append("- No overrides â€” use Master spacing")
    lines.append("")
    # Typography Overrides
    lines.append("### Typography Overrides")
    lines.append("")
    typography = page_overrides.get("typography", {})
    if typography:
        for key, value in typography.items():
            lines.append(f"- **{key}:** {value}")
    else:
        lines.append("- No overrides â€” use Master typography")
    lines.append("")
    # Color Overrides
    lines.append("### Color Overrides")
    lines.append("")
    colors = page_overrides.get("colors", {})
    if colors:
        for key, value in colors.items():
            lines.append(f"- **{key}:** {value}")
    else:
        lines.append("- No overrides â€” use Master colors")
    lines.append("")
    # Component Overrides
    lines.append("### Component Overrides")
    lines.append("")
    components = page_overrides.get("components", [])
    if components:
        for comp in components:
            lines.append(f"- {comp}")
    else:
        lines.append("- No overrides â€” use Master component specs")
    lines.append("")
    # Page-Specific Components
    lines.append("---")
    lines.append("")
    lines.append("## Page-Specific Components")
    lines.append("")
    unique_components = page_overrides.get("unique_components", [])
    if unique_components:
        for comp in unique_components:
            lines.append(f"- {comp}")
    else:
        lines.append("- No unique components for this page")
    lines.append("")
    # Recommendations
    lines.append("---")
    lines.append("")
    lines.append("## Recommendations")
    lines.append("")
    recommendations = page_overrides.get("recommendations", [])
    if recommendations:
        for rec in recommendations:
            lines.append(f"- {rec}")
    lines.append("")
    return "\n".join(lines)
def _generate_intelligent_overrides(page_name: str, page_query: str, design_system: dict) -> dict:
    """
    Generate intelligent overrides based on page type using layered search.
    Uses the existing search infrastructure to find relevant style, UX, and layout
    data instead of hardcoded page types.
    """
    from core import search
    page_lower = page_name.lower()
    query_lower = (page_query or "").lower()
    combined_context = f"{page_lower} {query_lower}"
    # Search across multiple domains for page-specific guidance
    style_search = search(combined_context, "style", max_results=1)
    ux_search = search(combined_context, "ux", max_results=3)
    landing_search = search(combined_context, "landing", max_results=1)
    # Extract results from search response
    style_results = style_search.get("results", [])
    ux_results = ux_search.get("results", [])
    landing_results = landing_search.get("results", [])
    # Detect page type from search results or context
    page_type = _detect_page_type(combined_context, style_results)
    # Build overrides from search results
    layout = {}
    spacing = {}
    typography = {}
    colors = {}
    components = []
    unique_components = []
    recommendations = []
    # Extract style-based overrides
    if style_results:
        style = style_results[0]
        style_name = style.get("Style Category", "")
        keywords = style.get("Keywords", "")
        best_for = style.get("Best For", "")
        effects = style.get("Effects & Animation", "")
        # Infer layout from style keywords
        if any(kw in keywords.lower() for kw in ["data", "dense", "dashboard", "grid"]):
            layout["Max Width"] = "1400px or full-width"
            layout["Grid"] = "12-column grid for data flexibility"
            spacing["Content Density"] = "High â€” optimize for information display"
        elif any(kw in keywords.lower() for kw in ["minimal", "simple", "clean", "single"]):
            layout["Max Width"] = "800px (narrow, focused)"
            layout["Layout"] = "Single column, centered"
            spacing["Content Density"] = "Low â€” focus on clarity"
        else:
            layout["Max Width"] = "1200px (standard)"
            layout["Layout"] = "Full-width sections, centered content"
        if effects:
            recommendations.append(f"Effects: {effects}")
    # Extract UX guidelines as recommendations
    for ux in ux_results:
        category = ux.get("Category", "")
        do_text = ux.get("Do", "")
        dont_text = ux.get("Don't", "")
        if do_text:
            recommendations.append(f"{category}: {do_text}")
        if dont_text:
            components.append(f"Avoid: {dont_text}")
    # Extract landing pattern info for section structure
    if landing_results:
        landing = landing_results[0]
        sections = landing.get("Section Order", "")
        cta_placement = landing.get("Primary CTA Placement", "")
        color_strategy = landing.get("Color Strategy", "")
        if sections:
            layout["Sections"] = sections
        if cta_placement:
            recommendations.append(f"CTA Placement: {cta_placement}")
        if color_strategy:
            colors["Strategy"] = color_strategy
    # Add page-type specific defaults if no search results
    if not layout:
        layout["Max Width"] = "1200px"
        layout["Layout"] = "Responsive grid"
    if not recommendations:
        recommendations = [
            "Refer to MASTER.md for all design rules",
            "Add specific overrides as needed for this page"
        ]
    return {
        "page_type": page_type,
        "layout": layout,
        "spacing": spacing,
        "typography": typography,
        "colors": colors,
        "components": components,
        "unique_components": unique_components,
        "recommendations": recommendations
    }
def _detect_page_type(context: str, style_results: list) -> str:
    """Detect page type from context and search results."""
    context_lower = context.lower()
    # Check for common page type patterns
    page_patterns = [
        (["dashboard", "admin", "analytics", "data", "metrics", "stats", "monitor", "overview"], "Dashboard / Data View"),
        (["checkout", "payment", "cart", "purchase", "order", "billing"], "Checkout / Payment"),
        (["settings", "profile", "account", "preferences", "config"], "Settings / Profile"),
        (["landing", "marketing", "homepage", "hero", "home", "promo"], "Landing / Marketing"),
        (["login", "signin", "signup", "register", "auth", "password"], "Authentication"),
        (["pricing", "plans", "subscription", "tiers", "packages"], "Pricing / Plans"),
        (["blog", "article", "post", "news", "content", "story"], "Blog / Article"),
        (["product", "item", "detail", "pdp", "shop", "store"], "Product Detail"),
        (["search", "results", "browse", "filter", "catalog", "list"], "Search Results"),
        (["empty", "404", "error", "not found", "zero"], "Empty State"),
    ]
    for keywords, page_type in page_patterns:
        if any(kw in context_lower for kw in keywords):
            return page_type
    # Fallback: try to infer from style results
    if style_results:
        style_name = style_results[0].get("Style Category", "").lower()
        best_for = style_results[0].get("Best For", "").lower()
        if "dashboard" in best_for or "data" in best_for:
            return "Dashboard / Data View"
        elif "landing" in best_for or "marketing" in best_for:
            return "Landing / Marketing"
    return "General"
# ============ CLI SUPPORT ============
if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Generate Design System")
    parser.add_argument("query", help="Search query (e.g., 'SaaS dashboard')")
    parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name")
    parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format")
    args = parser.parse_args()
    result = generate_design_system(args.query, args.project_name, args.format)
    print(result)
.claude/skills/ui-ux-pro-max/scripts/search.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
UI/UX Pro Max Search - BM25 search engine for UI/UX style guides
Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--max-results 3]
       python search.py "<query>" --design-system [-p "Project Name"]
       python search.py "<query>" --design-system --persist [-p "Project Name"] [--page "dashboard"]
Domains: style, prompt, color, chart, landing, product, ux, typography
Stacks: html-tailwind, react, nextjs
Persistence (Master + Overrides pattern):
  --persist    Save design system to design-system/MASTER.md
  --page       Also create a page-specific override file in design-system/pages/
"""
import argparse
import sys
import io
from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, search_stack
from design_system import generate_design_system, persist_design_system
# Force UTF-8 for stdout/stderr to handle emojis on Windows (cp1252 default)
if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
if sys.stderr.encoding and sys.stderr.encoding.lower() != 'utf-8':
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
def format_output(result):
    """Format results for Claude consumption (token-optimized)"""
    if "error" in result:
        return f"Error: {result['error']}"
    output = []
    if result.get("stack"):
        output.append(f"## UI Pro Max Stack Guidelines")
        output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}")
    else:
        output.append(f"## UI Pro Max Search Results")
        output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
    output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
    for i, row in enumerate(result['results'], 1):
        output.append(f"### Result {i}")
        for key, value in row.items():
            value_str = str(value)
            if len(value_str) > 300:
                value_str = value_str[:300] + "..."
            output.append(f"- **{key}:** {value_str}")
        output.append("")
    return "\n".join(output)
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="UI Pro Max Search")
    parser.add_argument("query", help="Search query")
    parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
    parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help="Stack-specific search (html-tailwind, react, nextjs)")
    parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    # Design system generation
    parser.add_argument("--design-system", "-ds", action="store_true", help="Generate complete design system recommendation")
    parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name for design system output")
    parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format for design system")
    # Persistence (Master + Overrides pattern)
    parser.add_argument("--persist", action="store_true", help="Save design system to design-system/MASTER.md (creates hierarchical structure)")
    parser.add_argument("--page", type=str, default=None, help="Create page-specific override file in design-system/pages/")
    parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory for persisted files (default: current directory)")
    args = parser.parse_args()
    # Design system takes priority
    if args.design_system:
        result = generate_design_system(
            args.query,
            args.project_name,
            args.format,
            persist=args.persist,
            page=args.page,
            output_dir=args.output_dir
        )
        print(result)
        # Print persistence confirmation
        if args.persist:
            project_slug = args.project_name.lower().replace(' ', '-') if args.project_name else "default"
            print("\n" + "=" * 60)
            print(f"✅ Design system persisted to design-system/{project_slug}/")
            print(f"   ðŸ“„ design-system/{project_slug}/MASTER.md (Global Source of Truth)")
            if args.page:
                page_filename = args.page.lower().replace(' ', '-')
                print(f"   ðŸ“„ design-system/{project_slug}/pages/{page_filename}.md (Page Overrides)")
            print("")
            print(f"📖 Usage: When building a page, check design-system/{project_slug}/pages/[page].md first.")
            print(f"   If exists, its rules override MASTER.md. Otherwise, use MASTER.md.")
            print("=" * 60)
    # Stack search
    elif args.stack:
        result = search_stack(args.query, args.stack, args.max_results)
        if args.json:
            import json
            print(json.dumps(result, indent=2, ensure_ascii=False))
        else:
            print(format_output(result))
    # Domain search
    else:
        result = search(args.query, args.domain, args.max_results)
        if args.json:
            import json
            print(json.dumps(result, indent=2, ensure_ascii=False))
        else:
            print(format_output(result))
docs/SRMÄ£¿éǰ¶ËÁªµ÷·½°¸.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,400 @@
# SRM æ¨¡å— - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
| é¡µé¢ | è·¯ç”±è·¯å¾„ | è¯´æ˜Ž |
|------|----------|------|
| ä¾›åº”商管理 | `/srm/supplier` | ä¾›åº”商档案 CRUD、Excel å¯¼å…¥å¯¼å‡º |
| ä¾›åº”商准入 | `/srm/supplier-apply` | å‡†å…¥ç”³è¯·ã€å®¡æ ¸æµç¨‹ |
| èµ„质管理 | `/srm/supplier-certificate` | èµ„质证书管理、到期提醒 |
| ç»©æ•ˆè¯„ä»· | `/srm/supplier-evaluation` | å››ç»´åº¦è¯„价、综合评分 |
| æ‹›æ ‡ç®¡ç† | `/srm/tender-project` | æ‹›æ ‡é¡¹ç›®ã€ç‰©æ–™æ˜Žç»† |
| æŠ•标管理 | `/srm/tender-bid` | æŠ•标、报价 |
| è¯„标管理 | `/srm/bid-evaluation` | è¯„标、开标、排名 |
| å®šæ ‡ç®¡ç† | `/srm/bid-award` | å®šæ ‡å®¡æ‰¹ã€ç”Ÿæˆé‡‡è´­è®¢å• |
| é‡‡è´­ååŒ | `/srm/purchase-collaboration` | è®¢å•确认、交货计划 |
| ç»Ÿè®¡åˆ†æž | `/srm/statistics` | æ‹›æŠ•标统计 |
---
## ä¸€ã€ä¾›åº”商管理 (`/srm/supplier`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/supplier/create` | åˆ›å»ºä¾›åº”商 |
| PUT | `/srm/supplier/update` | æ›´æ–°ä¾›åº”商 |
| DELETE | `/srm/supplier/delete?id={id}` | åˆ é™¤ä¾›åº”商 |
| GET | `/srm/supplier/get?id={id}` | èŽ·å–è¯¦æƒ… |
| GET | `/srm/supplier/page` | åˆ†é¡µæŸ¥è¯¢ |
| GET | `/srm/supplier/simple-list` | ç®€å•列表(下拉选择用) |
### è¯·æ±‚参数 - åˆ›å»º/更新
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ›´æ–°æ—¶å¿…å¡« | ä¾›åº”商ID |
| code | String | æ˜¯ | ä¾›åº”商编码 |
| name | String | æ˜¯ | ä¾›åº”商名称 |
| type | Integer | æ˜¯ | ç±»åž‹ï¼š1生产型/2贸易型/3服务型 |
| status | Integer | å¦ | çŠ¶æ€ï¼š0禁用/1启用 |
| contactName | String | å¦ | è”系人 |
| contactMobile | String | å¦ | è”系电话 |
| contactEmail | String | å¦ | é‚®ç®± |
| address | String | å¦ | åœ°å€ |
| bankName | String | å¦ | å¼€æˆ·é“¶è¡Œ |
| bankAccount | String | å¦ | é“¶è¡Œè´¦å· |
| taxNo | String | å¦ | ç¨Žå· |
| erpSupplierId | Long | å¦ | å…³è”ERP供应商ID |
| remark | String | å¦ | å¤‡æ³¨ |
### åˆ†é¡µæŸ¥è¯¢å‚æ•°
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | å¦ | ç¼–码(模糊) |
| name | String | å¦ | åç§°ï¼ˆæ¨¡ç³Šï¼‰ |
| type | Integer | å¦ | ç±»åž‹ |
| status | Integer | å¦ | çŠ¶æ€ |
| contactName | String | å¦ | è”系人(模糊) |
| contactMobile | String | å¦ | è”系电话(模糊) |
| pageNo | Integer | æ˜¯ | é¡µç  |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ¡æ•° |
### å“åº”字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | Long | ä¾›åº”商ID |
| code | String | ç¼–码 |
| name | String | åç§° |
| type | Integer | ç±»åž‹ |
| status | Integer | çŠ¶æ€ |
| contactName | String | è”系人 |
| contactMobile | String | è”系电话 |
| contactEmail | String | é‚®ç®± |
| address | String | åœ°å€ |
| bankName | String | å¼€æˆ·é“¶è¡Œ |
| bankAccount | String | é“¶è¡Œè´¦å· |
| taxNo | String | ç¨Žå· |
| erpSupplierId | Long | å…³è”ERP供应商ID |
| remark | String | å¤‡æ³¨ |
| createTime | DateTime | åˆ›å»ºæ—¶é—´ |
### ä¸šåŠ¡è§„åˆ™
| åœºæ™¯ | è§„则 |
|------|------|
| åˆ é™¤ | çº§è”删除联系人和分类关联 |
| ç¼–码 | å”¯ä¸€æ ¡éªŒï¼Œé‡å¤æ—¶åŽç«¯è¿”回错误 |
| çŠ¶æ€ | ç¦ç”¨çš„供应商在下拉列表中不显示 |
---
## äºŒã€ä¾›åº”商分类 (`/srm/supplier-category`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/supplier-category/assign` | åˆ†é…åˆ†ç±» |
| DELETE | `/srm/supplier-category/remove?id={id}` | ç§»é™¤åˆ†ç±» |
| GET | `/srm/supplier-category/list-by-supplier?supplierId={id}` | æŸ¥è¯¢ä¾›åº”商的分类列表 |
### åˆ†é…åˆ†ç±»å‚æ•°
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| supplierId | Long | æ˜¯ | ä¾›åº”商ID |
| categoryId | Long | æ˜¯ | åˆ†ç±»ID |
| categoryName | String | æ˜¯ | åˆ†ç±»åç§° |
---
## ä¸‰ã€ä¾›åº”商准入 (`/srm/supplier-apply`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/supplier-apply/create` | åˆ›å»ºç”³è¯· |
| PUT | `/srm/supplier-apply/update` | æ›´æ–°ç”³è¯· |
| DELETE | `/srm/supplier-apply/delete?id={id}` | åˆ é™¤ç”³è¯· |
| GET | `/srm/supplier-apply/get?id={id}` | èŽ·å–è¯¦æƒ… |
| GET | `/srm/supplier-apply/page` | åˆ†é¡µæŸ¥è¯¢ |
| PUT | `/srm/supplier-apply/submit?id={id}` | æäº¤å®¡æ ¸ |
| PUT | `/srm/supplier-apply/review-purchase` | é‡‡è´­å®¡æ ¸ |
| PUT | `/srm/supplier-apply/review-quality` | è´¨é‡å®¡æ ¸ |
| PUT | `/srm/supplier-apply/review-finance` | è´¢åŠ¡å®¡æ ¸ |
### çŠ¶æ€æµè½¬
```
DRAFT(草稿) â†’ æäº¤ â†’ PENDING_PURCHASE(待采购审核)
                       â†’ é€šè¿‡ â†’ PENDING_QUALITY(待质量审核)
                                â†’ é€šè¿‡ â†’ PENDING_FINANCE(待财务审核)
                                         â†’ é€šè¿‡ â†’ APPROVED(已通过) â†’ è‡ªåŠ¨åˆ›å»ºä¾›åº”å•†
                          â†’ æ‹’绝 â†’ REJECTED(已拒绝)
```
### å­—段展示规则
| å­—段 | åˆ—表 | è¯¦æƒ… | ç¼–辑 |
|------|------|------|------|
| applyNo (申请编号) | âœ… | âœ… | è‡ªåŠ¨ç”Ÿæˆ AP + yyyyMM + 4位随机数 |
| supplierName (供应商名称) | âœ… | âœ… | âœ… |
| applyStatus (状态) | âœ… (标签颜色) | âœ… | åªè¯» |
| contactName (联系人) | âœ… | âœ… | âœ… |
| contactMobile (联系电话) | âœ… | âœ… | âœ… |
| businessLicense (营业执照) | - | âœ… | âœ… é™„件上传 |
| taxCertificate (税务登记证) | - | âœ… | âœ… é™„件上传 |
| orgCodeCertificate (组织机构代码证) | - | âœ… | âœ… é™„件上传 |
| auditComment (审核意见) | - | âœ… | âœ… å®¡æ ¸æ—¶å¡«å†™ |
### å®¡æ ¸æŽ¥å£å‚æ•° (`review-*`)
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | ç”³è¯·ID |
| passed | Boolean | æ˜¯ | æ˜¯å¦é€šè¿‡ |
| comment | String | å¦ | å®¡æ ¸æ„è§ |
### ä¸šåŠ¡è§„åˆ™
| åœºæ™¯ | è§„则 |
|------|------|
| æäº¤ | ä»…草稿状态可提交 |
| å®¡æ ¸ | ä»…对应审核中状态可审核 |
| é€šè¿‡åŽ | è‡ªåŠ¨åˆ›å»ºä¾›åº”å•†è®°å½•åˆ° srm_supplier |
| é™„ä»¶ | è¥ä¸šæ‰§ç…§ã€ç¨ŽåŠ¡ç™»è®°è¯ç­‰ä½¿ç”¨ system æ¨¡å— storage API |
---
## å››ã€èµ„质管理 (`/srm/supplier-certificate`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/supplier-certificate/create` | åˆ›å»ºèµ„è´¨ |
| PUT | `/srm/supplier-certificate/update` | æ›´æ–°èµ„è´¨ |
| DELETE | `/srm/supplier-certificate/delete?id={id}` | åˆ é™¤èµ„è´¨ |
| GET | `/srm/supplier-certificate/get?id={id}` | èŽ·å–è¯¦æƒ… |
| GET | `/srm/supplier-certificate/page` | åˆ†é¡µæŸ¥è¯¢ |
| GET | `/srm/supplier-certificate/list-by-supplier?supplierId={id}` | æŒ‰ä¾›åº”商查询 |
| GET | `/srm/supplier-certificate/expiring-list` | å³å°†åˆ°æœŸåˆ—表(30天内) |
### åˆ°æœŸçŠ¶æ€è‡ªåŠ¨è®¡ç®—
前端根据 `expireDate` ä¸Žå½“前日期对比展示:
- å·²è¿‡æœŸï¼ˆçº¢è‰²ï¼‰ï¼š`expireDate < now`
- å³å°†åˆ°æœŸï¼ˆæ©™è‰²ï¼‰ï¼š`expireDate - now <= 30天`
- æ­£å¸¸ï¼ˆç»¿è‰²ï¼‰ï¼š`expireDate - now > 30天`
### æ³¨æ„äº‹é¡¹
- èµ„质证书支持附件上传,使用 system æ¨¡å— Storage API
- åˆ°æœŸæé†’:列表页面默认可以高亮即将到期的记录
- `expiring-list` æŽ¥å£è¿”回所有30天内到期的资质,可用于首页提醒卡片
---
## äº”、绩效评价 (`/srm/supplier-evaluation`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/supplier-evaluation/create` | åˆ›å»ºè¯„ä»· |
| PUT | `/srm/supplier-evaluation/update` | æ›´æ–°è¯„ä»· |
| DELETE | `/srm/supplier-evaluation/delete?id={id}` | åˆ é™¤è¯„ä»· |
| GET | `/srm/supplier-evaluation/get?id={id}` | èŽ·å–è¯¦æƒ… |
| GET | `/srm/supplier-evaluation/page` | åˆ†é¡µæŸ¥è¯¢ |
### è¯„分计算规则(后端自动计算)
- **综合评分** = è´¨é‡è¯„分 Ã— 40% + äº¤ä»˜è¯„分 Ã— 30% + ä»·æ ¼è¯„分 Ã— 20% + æœåŠ¡è¯„åˆ† Ã— 10%
- **等级评定**:A级 â‰¥ 90,B级 â‰¥ 80,C级 â‰¥ 70,D级 < 70
### å­—段展示规则
| å­—段 | åˆ—表 | è¯´æ˜Ž |
|------|------|------|
| supplierName | âœ… | ä¾›åº”商名称 |
| evaluationPeriod | âœ… | è¯„价周期(如 "2024-Q1") |
| qualityScore | âœ… | è´¨é‡è¯„分 (0-100) |
| deliveryScore | âœ… | äº¤ä»˜è¯„分 (0-100) |
| priceScore | âœ… | ä»·æ ¼è¯„分 (0-100) |
| serviceScore | âœ… | æœåŠ¡è¯„åˆ† (0-100) |
| compositeScore | âœ… (彩色标签) | ç»¼åˆè¯„分 |
| grade | âœ… (等级标签) | A/B/C/D |
---
## å…­ã€æ‹›æ ‡ç®¡ç† (`/srm/tender-project`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/tender-project/create` | åˆ›å»ºæ‹›æ ‡ |
| PUT | `/srm/tender-project/update` | æ›´æ–°æ‹›æ ‡ |
| DELETE | `/srm/tender-project/delete?id={id}` | åˆ é™¤æ‹›æ ‡ |
| GET | `/srm/tender-project/get?id={id}` | èŽ·å–è¯¦æƒ… |
| GET | `/srm/tender-project/page` | åˆ†é¡µæŸ¥è¯¢ |
| PUT | `/srm/tender-project/publish?id={id}` | å‘布招标 |
| PUT | `/srm/tender-project/close?id={id}` | å…³é—­æ‹›æ ‡ |
| PUT | `/srm/tender-project/start-bidding?id={id}` | å¼€å§‹æŠ•æ ‡ |
| PUT | `/srm/tender-project/start-evaluation?id={id}` | å¼€å§‹è¯„æ ‡ |
### ç‰©æ–™å­è¡¨
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/tender-project/material/create` | æ·»åŠ ç‰©æ–™ |
| PUT | `/srm/tender-project/material/update` | æ›´æ–°ç‰©æ–™ |
| DELETE | `/srm/tender-project/material/delete?id={id}` | åˆ é™¤ç‰©æ–™ |
| GET | `/srm/tender-project/material/list?tenderProjectId={id}` | èŽ·å–ç‰©æ–™åˆ—è¡¨ |
### çŠ¶æ€æµè½¬
```
DRAFT(草稿) â†’ PUBLISHED(已发布) â†’ BIDDING(投标中) â†’ EVALUATING(评标中) â†’ CLOSED(已关闭)
```
### ä¸šåŠ¡è§„åˆ™
| åœºæ™¯ | è§„则 |
|------|------|
| å‘布 | ä»…草稿状态可发布 |
| å¼€å§‹æŠ•æ ‡ | å·²å‘布后可进入投标阶段 |
| å¼€å§‹è¯„æ ‡ | æŠ•标中可进入评标阶段 |
| å…³é—­ | ä»»æ„éžè‰ç¨¿çŠ¶æ€å‡å¯å…³é—­ |
| ç‰©æ–™ | æ‹›æ ‡é¡¹ç›®åˆ é™¤æ—¶çº§è”删除所有物料 |
---
## ä¸ƒã€æŠ•标与报价 (`/srm/tender-bid`)
### API - æŠ•æ ‡
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/tender-bid/create` | åˆ›å»ºæŠ•æ ‡ |
| GET | `/srm/tender-bid/get?id={id}` | èŽ·å–æŠ•æ ‡è¯¦æƒ… |
| GET | `/srm/tender-bid/list-by-project?tenderProjectId={id}` | èŽ·å–é¡¹ç›®çš„æŠ•æ ‡åˆ—è¡¨ |
| PUT | `/srm/tender-bid/withdraw?id={id}` | æ’¤æ ‡ |
### API - æŠ¥ä»·
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/tender-bid/quote/create` | åˆ›å»ºæŠ¥ä»· |
| PUT | `/srm/tender-bid/quote/update` | æ›´æ–°æŠ¥ä»· |
| GET | `/srm/tender-bid/quote/list-by-bid?bidId={id}` | èŽ·å–æŠ•æ ‡çš„æŠ¥ä»·åˆ—è¡¨ |
### æ•°æ®å¸¦å…¥è¯´æ˜Ž
- ä¾›åº”商管理 -> æŠ•标:选择供应商时从 `srm_supplier` èŽ·å–ä¾›åº”å•†åˆ—è¡¨
- æ‹›æ ‡é¡¹ç›® -> æŠ•标:`tenderProjectId` ä»Žæ‹›æ ‡è¯¦æƒ…页传入
- æŠ•æ ‡ -> æŠ¥ä»·ï¼š`bidId` ä»ŽæŠ•标记录传入
---
## å…«ã€è¯„标与开标 (`/srm/bid-evaluation`)
### API - è¯„æ ‡
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/bid-evaluation/create` | åˆ›å»ºè¯„标记录 |
| GET | `/srm/bid-evaluation/list-by-project?tenderProjectId={id}` | èŽ·å–è¯„æ ‡åˆ—è¡¨ |
| PUT | `/srm/bid-evaluation/calculate-ranking?tenderProjectId={id}` | è‡ªåŠ¨è®¡ç®—æŽ’å |
### API - å¼€æ ‡
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/bid-evaluation/bid-open/create?tenderProjectId={id}` | å¼€æ ‡ |
| GET | `/srm/bid-evaluation/bid-open/get?id={id}` | èŽ·å–å¼€æ ‡è®°å½• |
### ä¸šåŠ¡æµç¨‹
```
投标截止 â†’ å¼€æ ‡ï¼ˆcreateBidOpen)→ å½•入评标分数 â†’ è®¡ç®—排名(calculateRanking) â†’ å®šæ ‡
```
---
## ä¹ã€å®šæ ‡ (`/srm/bid-award`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/srm/bid-award/create` | åˆ›å»ºå®šæ ‡ |
| GET | `/srm/bid-award/get?id={id}` | èŽ·å–å®šæ ‡è¯¦æƒ… |
| PUT | `/srm/bid-award/approve?id={id}` | å®¡æ‰¹å®šæ ‡ |
### åŽç»­æµç¨‹
定标审批通过后,调用 ERP æ¨¡å—接口生成采购订单(后端已预留接口 `generatePurchaseOrder`)。
---
## åã€ç»Ÿè®¡åˆ†æž (`/srm/statistics`)
### API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/srm/statistics/tender?year={year}` | èŽ·å–å¹´åº¦æ‹›æ ‡ç»Ÿè®¡ |
### å“åº”字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| totalTenderCount | Long | æ‹›æ ‡æ€»æ•° |
| totalTenderAmount | BigDecimal | æ‹›æ ‡æ€»é‡‘额(预算) |
| totalAwardAmount | BigDecimal | å®šæ ‡æ€»é‡‘额 |
| savedAmount | BigDecimal | èŠ‚çº¦é‡‘é¢ï¼ˆé¢„ç®—-定标) |
| winRate | BigDecimal | ä¸­æ ‡çއ(%) |
| supplierCount | Long | å‚与供应商数 |
| period | String | ç»Ÿè®¡å¹´ä»½ |
---
## æ³¨æ„äº‹é¡¹
1. **附件上传**:所有文件上传统一使用 `system` æ¨¡å—çš„ Storage API:
   - ä¸Šä¼ ï¼š`POST /system/storage-blob/upload`
   - ç»‘定:`POST /system/storage-attachment/bind`
   - æŸ¥è¯¢ï¼š`GET /system/storage-attachment/list`
   - recordType æ ¼å¼ï¼š`srm_{业务表名}`,如 `srm_supplier_certificate`、`srm_tender_bid`
2. **权限前缀**:
   - ä¾›åº”商:`srm:supplier`
   - å‡†å…¥ï¼š`srm:supplier-apply`
   - èµ„质:`srm:supplier-certificate`
   - è¯„价:`srm:supplier-evaluation`
   - æ‹›æ ‡ï¼š`srm:tender-project`
   - æŠ•标:`srm:tender-bid`
   - æŠ¥ä»·ï¼š`srm:supplier-quote`
   - è¯„标:`srm:bid-evaluation`
   - å¼€æ ‡ï¼š`srm:bid-open`
   - å®šæ ‡ï¼š`srm:bid-award`
   - ç»Ÿè®¡ï¼š`srm:statistics`
3. **下拉选择**:供应商下拉使用 `/srm/supplier/simple-list` æŽ¥å£ï¼Œåªè¿”回启用状态的供应商
4. **状态标签颜色建议**:
   - è‰ç¨¿ï¼šç°è‰²
   - å®¡æ ¸ä¸­/投标中:蓝色
   - å·²é€šè¿‡/已发布:绿色
   - å·²æ‹’绝/已关闭:红色
   - å·²è¿‡æœŸï¼šçº¢è‰²
5. **编号生成规则**:
   - å‡†å…¥ç”³è¯·ç¼–号:`AP` + yyyyMM + 4位随机数(后端自动生成)
   - æŠ•标编号、定标编号:后端自动生成
docs/sql/unify_supplier_to_srm.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
-- ============================================
-- ä¾›åº”商统一迁移至 SRM
-- å°† ERP/MES è¡¨çš„ supplier_id/vendor_id ä»Ž erp_supplier.id è¿ç§»ä¸º srm_supplier.id
-- åˆ é™¤ srm_supplier.erp_supplier_id æ¡¥æŽ¥å­—段
-- æ‰§è¡Œå‰è¯·å¤‡ä»½æ•°æ®åº“!
-- ============================================
-- 1. è¿ç§» ERP è¡¨ supplier_id (erp_supplier.id â†’ srm_supplier.id)
UPDATE erp_purchase_request r
INNER JOIN srm_supplier s ON s.erp_supplier_id = r.supplier_id
SET r.supplier_id = s.id;
UPDATE erp_purchase_order o
INNER JOIN srm_supplier s ON s.erp_supplier_id = o.supplier_id
SET o.supplier_id = s.id;
UPDATE erp_purchase_in i
INNER JOIN srm_supplier s ON s.erp_supplier_id = i.supplier_id
SET i.supplier_id = s.id;
UPDATE erp_purchase_return r
INNER JOIN srm_supplier s ON s.erp_supplier_id = r.supplier_id
SET r.supplier_id = s.id;
UPDATE erp_finance_payment p
INNER JOIN srm_supplier s ON s.erp_supplier_id = p.supplier_id
SET p.supplier_id = s.id;
UPDATE erp_stock_in si
INNER JOIN srm_supplier s ON s.erp_supplier_id = si.supplier_id
SET si.supplier_id = s.id;
-- 2. è¿ç§» MES è¡¨ vendor_id (erp_supplier.id â†’ srm_supplier.id)
UPDATE mes_wm_arrival_notice a
INNER JOIN srm_supplier s ON s.erp_supplier_id = a.vendor_id
SET a.vendor_id = s.id;
UPDATE mes_wm_item_receipt r
INNER JOIN srm_supplier s ON s.erp_supplier_id = r.vendor_id
SET r.vendor_id = s.id;
UPDATE mes_wm_outsource_receipt r
INNER JOIN srm_supplier s ON s.erp_supplier_id = r.vendor_id
SET r.vendor_id = s.id;
UPDATE mes_wm_outsource_issue i
INNER JOIN srm_supplier s ON s.erp_supplier_id = i.vendor_id
SET i.vendor_id = s.id;
UPDATE mes_wm_return_vendor r
INNER JOIN srm_supplier s ON s.erp_supplier_id = r.vendor_id
SET r.vendor_id = s.id;
UPDATE mes_qc_iqc q
INNER JOIN srm_supplier s ON s.erp_supplier_id = q.vendor_id
SET q.vendor_id = s.id;
UPDATE mes_pro_work_order w
INNER JOIN srm_supplier s ON s.erp_supplier_id = w.vendor_id
SET w.vendor_id = s.id;
UPDATE mes_wm_material_stock ms
INNER JOIN srm_supplier s ON s.erp_supplier_id = ms.vendor_id
SET ms.vendor_id = s.id;
UPDATE mes_wm_batch b
INNER JOIN srm_supplier s ON s.erp_supplier_id = b.vendor_id
SET b.vendor_id = s.id;
-- 3. åˆ é™¤ srm_supplier çš„ erp_supplier_id æ¡¥æŽ¥å­—段
ALTER TABLE srm_supplier DROP INDEX idx_erp_supplier_id;
ALTER TABLE srm_supplier DROP COLUMN erp_supplier_id;
-- 4. srm_supplier_apply å¦‚果存在 erp_supplier_id åˆ™åˆ é™¤
ALTER TABLE srm_supplier_apply DROP COLUMN IF EXISTS erp_supplier_id;
docs/srm_supplier_apply_select.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
# SRM å‡†å…¥ç”³è¯·é€‰æ‹©ä¾›åº”商 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- `src/views/srm/supplierApply/index.vue` â€” å‡†å…¥ç”³è¯·åˆ—表页
- `src/views/srm/supplierApply/modules/form.vue` â€” å‡†å…¥ç”³è¯·æ–°å¢ž/编辑弹窗
- `src/views/srm/supplierApply/data.ts` â€” è¡¨å•和表格配置
## ä¸šåŠ¡æµç¨‹
1. ç”¨æˆ·ç‚¹å‡»"新增准入申请" â†’ æ‰“开表单弹窗
2. ç”¨æˆ·é€šè¿‡**SRM ä¾›åº”商选择器**从已有 SRM ä¾›åº”商列表中选择
3. é€‰æ‹©åŽï¼Œç»„件通过 `onChange` å›žè°ƒè‡ªåŠ¨å¡«å……è¡¨å•å­—æ®µ
4. ç”¨æˆ·å¯æ‰‹åŠ¨ä¿®æ”¹è‡ªåŠ¨å¡«å……çš„å­—æ®µï¼Œè¡¥å……ç¼ºå¤±ä¿¡æ¯
5. æäº¤ç”³è¯· â†’ ä¿å­˜ `supplierId` åˆ°åŽç«¯
6. å®¡æ‰¹é€šè¿‡æ—¶ï¼Œè‹¥å·²å…³è” SRM ä¾›åº”商则不重复创建;若未关联则根据申请信息创建新的 SRM ä¾›åº”商
## API
### æ¶‰åŠæŽ¥å£
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/srm/supplier/page` | SRM ä¾›åº”商分页列表(供应商选择器使用) |
| POST | `/srm/supplier-apply/create` | åˆ›å»ºå‡†å…¥ç”³è¯· |
| PUT | `/srm/supplier-apply/update` | æ›´æ–°å‡†å…¥ç”³è¯· |
| GET | `/srm/supplier-apply/get?id={id}` | èŽ·å–å‡†å…¥ç”³è¯·è¯¦æƒ… |
### åˆ›å»º/更新准入申请 â€” è¯·æ±‚参数
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| supplierId | Long | å¦ | å…³è”çš„ SRM ä¾›åº”商 ID。与 supplierName äºŒé€‰ä¸€å¿…å¡« |
| supplierName | String | æ¡ä»¶å¿…å¡« | ä¾›åº”商名称。supplierId ä¸ºç©ºæ—¶å¿…å¡« |
### æ ¡éªŒè§„则变更
- `supplierName` ä¸å†å¼ºåˆ¶å¿…填。当 `supplierId` ä¸ºç©ºæ—¶ï¼Œ`supplierName` å¿…å¡«
- å½“ `supplierId` æœ‰å€¼æ—¶ï¼ŒåŽç«¯ä¼šæ ¡éªŒè¯¥ä¾›åº”商在 SRM ä¸­å­˜åœ¨
## å­—段自动填充规则
选择 SRM ä¾›åº”商后,组件的 `onChange` å›žè°ƒæŽ¥æ”¶ `SupplierVO` å¯¹è±¡ï¼ŒæŒ‰ä»¥ä¸‹æ˜ å°„自动填充:
| SRM ä¾›åº”商字段 | å‡†å…¥ç”³è¯·è¡¨å•字段 | è¯´æ˜Ž |
|------|------|------|
| `name` | `supplierName` | ä¾›åº”商名称 |
| `contactName` | `contactName` | è”系人 |
| `contactMobile` | `contactMobile` | è”系电话 |
| `contactEmail` | `contactEmail` | é‚®ç®± |
| `taxNo` | `taxNo` | ç¨Žå· |
| `address` | `address` | åœ°å€ |
## å‰ç«¯å®žçŽ°è¯´æ˜Ž
### 1. è¡¨å• schema(`data.ts`)
表单字段 `supplierId` ä½¿ç”¨ `SrmSupplierSelect` ç»„件:
- ç»„件路径:`src/views/srm/supplier/components/select.vue`
- `modelValue` ç»‘定到 `supplierId`(number ç±»åž‹ï¼‰
- é€šè¿‡ `onChange` å›žè°ƒæŽ¥æ”¶é€‰ä¸­çš„ `SupplierVO`,调用 `formApi.setValues()` è‡ªåЍ填充
- `supplierName` prop ç”¨äºŽå›žæ˜¾å·²é€‰ä¾›åº”商名称
- å­—段 `supplierName` ä¿ç•™åœ¨è¡¨å•中,由选择器自动填充或被用户手动输入
### 2. formApi æ³¨å…¥ï¼ˆ`form.vue`)
使用 `formApi.setState({ schema: useFormSchema(formApi) })` æ¨¡å¼ï¼Œåœ¨ `useVbenForm` åˆ›å»ºå®ŒæˆåŽå°† `formApi` æ³¨å…¥ schema,使 `onChange` å›žè°ƒèƒ½è°ƒç”¨ `formApi.setValues()`。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| supplierId | è¯¦æƒ… | å…³è”çš„ SRM ä¾›åº”商 ID |
| supplierName | åˆ—表、详情 | ä¾›åº”商名称(选择器自动填充或手动输入) |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| é€‰æ‹©äº† SRM ä¾›åº”商 | `supplierId` æœ‰å€¼ï¼Œä¿¡æ¯ä»Ž SRM è‡ªåŠ¨å¸¦å…¥ï¼ŒåŽç«¯æ ¡éªŒä¾›åº”å•†å­˜åœ¨ |
| æœªé€‰æ‹© SRM ä¾›åº”商 | `supplierId` ä¸ºç©ºï¼Œå¿…须手动填写供应商名称 |
| é‡æ–°é€‰æ‹©ä¾›åº”商 | ç»„ä»¶ `onChange` é‡æ–°è§¦å‘,覆盖已有表单字段 |
| ç¼–辑已有申请 | é€šè¿‡ `supplierName` prop å›žæ˜¾é€‰ä¸­ä¾›åº”商名称 |
| å®¡æ‰¹é€šè¿‡ï¼ˆå·²å…³è” SRM ä¾›åº”商) | ä¸åˆ›å»ºæ–°çš„ SRM ä¾›åº”商,直接使用已有 `supplierId` |
| å®¡æ‰¹é€šè¿‡ï¼ˆæœªå…³è” SRM ä¾›åº”商) | åŽç«¯æ ¹æ®ç”³è¯·ä¿¡æ¯åˆ›å»ºæ–°çš„ SRM ä¾›åº”商 |
## æ³¨æ„äº‹é¡¹
- SRM ä¾›åº”商选择器组件位于 `src/views/srm/supplier/components/`,已新建
- è‡ªåŠ¨å¡«å……åŽç”¨æˆ·ä»å¯æ‰‹åŠ¨ä¿®æ”¹ä»»ä½•å­—æ®µ
- ä¾›åº”商选择器仅查询启用状态(`status: 1`)的供应商
- `supplierId` å’Œ `supplierName` äº’斥校验:两者至少有一个有值
docs/Ä£¿é¹¦ÄܽéÉÜÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,513 @@
# MOM åˆ¶é€ è¿è¥ç®¡ç†ç³»ç»Ÿ - æ¨¡å—功能介绍
## é¡¹ç›®æ¦‚è¿°
本项目是基于芋道框架(Yudao)构建的**制造企业综合管理系统(MOM)**,集成了 MES、ERP、CRM、HRM、MDM、BPM ç­‰ä¼ä¸šçº§ä¸šåŠ¡æ¨¡å—ï¼Œè¦†ç›–åˆ¶é€ ä¼ä¸šä»Žé”€å”®ã€é‡‡è´­ã€ç”Ÿäº§ã€è´¨é‡ã€ä»“å‚¨åˆ°è®¾å¤‡ç®¡ç†çš„å…¨ä¸šåŠ¡æµç¨‹ã€‚
- **技术栈**:Spring Boot + MySQL 8.4 + Flowable 6 + Redis
- **项目版本**:2026.06-jdk25-SNAPSHOT
---
## ä¸€ã€è¥é”€ç®¡ç†
营销管理以 **CRM(客户关系管理)** æ¨¡å—为核心,覆盖从线索获取到合同回款的完整销售流程。
### 1.1 å®¢æˆ·ç®¡ç†
系统搭建完整的客户资源管理体系。支持统一维护客户档案信息,覆盖客户名称、所属地区、行业、客户等级、获客来源等基础资料,并提供客户数据导入、导出能力,方便信息批量维护与数据备份。内置客户公海运营机制,未及时跟进、尚未成交的客户可自动回收至公海资源池,支持客户领取、手动分配、客户资源转移等操作,盘活闲置客户资源。同时具备客户持有管控与锁定能力,管理员可配置每名销售能够持有、锁定的客户数量上限,避免少数人员囤积垄断客户;提供客户锁定功能,被锁定客户其他销售无法领取,保障跟进人员正常开展业务,实现客户资源规范化流转。
- å®¢æˆ·æ¡£æ¡ˆï¼šç®¡ç†å®¢æˆ·åŸºæœ¬ä¿¡æ¯ï¼ˆåç§°ã€åœ°åŒºã€è¡Œä¸šã€ç­‰çº§ã€æ¥æºï¼‰ï¼Œæ”¯æŒå¯¼å…¥å¯¼å‡º
- å…¬æµ·æœºåˆ¶ï¼šæœªè·Ÿè¿›/未成交客户自动回收到公海,支持领取、分配、转移
- å®¢æˆ·é™åˆ¶ï¼šå¯é…ç½®æ¯ä¸ªå‘˜å·¥æ‹¥æœ‰/锁定的客户数上限,防止垄断
- å®¢æˆ·é”å®šï¼šé”å®šå®¢æˆ·é˜²æ­¢è¢«å…¶ä»–销售领取
### 1.2 çº¿ç´¢ä¸Žè”系人
系统提供从线索到客户的完整转化路径。支持录入和跟踪销售线索,记录线索来源、意向产品、跟进状态等信息,线索确认后可一键转化为正式客户,自动带入已有的联系信息和跟进记录,减少重复录入。联系人管理支持在客户下维护多个联系人档案,记录姓名、职务、手机、邮箱等联系信息,并可标记关键决策人,帮助销售人员精准定位沟通对象,提升跟进效率。
- çº¿ç´¢ç®¡ç†ï¼šå½•入和跟踪销售线索,支持线索转化为客户
- è”系人管理:管理客户下的联系人,标记关键决策人,记录联系信息
### 1.3 å•†æœºä¸ŽæŠ¥ä»·
系统支撑从商机识别到报价签约的销售推进过程。商机管理支持对潜在销售机会进行全生命周期跟踪,定义商机阶段(如初步接触、需求确认、方案报价、商务谈判等),每个阶段可配置流转条件和审批规则,帮助销售团队规范化推进商机。报价单管理支持快速生成销售报价,自动关联客户信息和产品明细,支持折扣、税率、有效期等商务条款配置,报价审批通过后可关联生成正式合同,减少重复制单。
- å•†æœºç®¡ç†ï¼šè·Ÿè¸ªæ½œåœ¨é”€å”®æœºä¼šï¼Œç®¡ç†å•†æœºé˜¶æ®µå’ŒçŠ¶æ€æµè½¬
- æŠ¥ä»·å•:生成销售报价单,记录产品明细、金额、折扣、有效期
- äº§å“æ˜Žç»†ï¼šå•†æœºå’ŒæŠ¥ä»·å‡æ”¯æŒå…³è”物料/产品,自动计算金额
### 1.4 åˆåŒä¸Žå›žæ¬¾
系统打通合同签约到资金回笼的完整闭环。合同管理支持在线创建销售合同,自动带入商机报价的产品明细和金额信息,可提交 BPM å·¥ä½œæµå®¡æ‰¹ï¼Œå®¡æ‰¹é€šè¿‡åŽé”å®šåˆåŒå†…容防止篡改。支持设置合同起止时间和到期提醒,避免合同逾期风险。回款计划支持按合同约定制定分期回款方案,每期设置计划回款日期和提醒天数,到期自动通知相关人员。回款记录支持登记每笔实际回款的金额、回款方式、回款日期,系统自动统计已回款金额和待回款金额,形成应收账款全景视图。合同支持直接生成 ERP é”€å”®è®¢å•,打通从签约到交付的业务链路。
- åˆåŒç®¡ç†ï¼šç®¡ç†é”€å”®åˆåŒï¼Œæ”¯æŒæäº¤ BPM å®¡æ‰¹ã€åˆ°æœŸæé†’
- å›žæ¬¾è®¡åˆ’:制定分期回款计划,设置提醒天数
- å›žæ¬¾è®°å½•:记录每笔回款的金额、方式、日期
- ERP é›†æˆï¼šåˆåŒå¯ç”Ÿæˆ ERP é”€å”®è®¢å•,打通销售到交付
### 1.5 é”€å”®åˆ†æž
系统内置多维度销售数据分析能力,为管理决策提供数据支撑。客户总量分析支持按日期、按用户统计新增客户数和成交客户数,直观反映客户增长趋势;客户跟进分析按日期、用户、跟进类型维度统计跟进次数,评估销售活动量;销售漏斗分析展示从客户到商机到赢单各阶段的数量与转化率,帮助识别流程瓶颈;员工业绩统计对合同数量、合同金额、回款金额进行同比环比分析,客观评估团队产出;客户画像分析提供地区分布、行业分布、级别分布、来源分布等多维统计,帮助企业了解客户结构;排行榜支持按合同金额、回款金额、签约数量、产品销量、新增客户数等维度生成 TOP æŽ’名,激发团队竞争意识;成交周期分析支持按日期、用户、区域、产品等维度分析从线索到成交的平均时长,辅助优化销售策略。
| åˆ†æžç±»åž‹ | åŠŸèƒ½è¯´æ˜Ž |
|----------|----------|
| å®¢æˆ·æ€»é‡åˆ†æž | æŒ‰æ—¥æœŸ/用户统计新增客户数、成交客户数 |
| å®¢æˆ·è·Ÿè¿›åˆ†æž | æŒ‰æ—¥æœŸ/用户/类型统计跟进次数 |
| é”€å”®æ¼æ–—分析 | å®¢æˆ·â†’商机→赢单 å„阶段数量与转化率 |
| å‘˜å·¥ä¸šç»©ç»Ÿè®¡ | åˆåŒæ•°é‡/金额、回款金额的同比环比分析 |
| å®¢æˆ·ç”»åƒåˆ†æž | åœ°åŒºåˆ†å¸ƒã€è¡Œä¸šåˆ†å¸ƒã€çº§åˆ«åˆ†å¸ƒã€æ¥æºåˆ†å¸ƒ |
| æŽ’行榜 | åˆåŒé‡‘额、回款金额、签约数、产品销量等 TOP æŽ’名 |
| æˆäº¤å‘¨æœŸåˆ†æž | æŒ‰æ—¥æœŸ/用户/区域/产品维度分析客户成交周期 |
### 1.6 è¾…助模块(已注释,可按需启用)
| æ¨¡å— | åŠŸèƒ½ |
|------|------|
| å•†åŸŽäº¤æ˜“ | è®¢å•管理、购物车、售后、物流配送、快递运费 |
| è¥é”€ä¿ƒé”€ | ä¼˜æƒ åˆ¸ã€ä¼˜æƒ ç ã€Banner å¹¿å‘Šã€è¥é”€æ´»åЍ |
| åˆ†é”€ç®¡ç† | æŽ¨å¹¿å‘˜ç»‘定、佣金计算、佣金提现 |
| ä¼šå‘˜ä¸­å¿ƒ | ä¼šå‘˜ç­‰çº§ã€ç§¯åˆ†ã€ç­¾åˆ°ã€æ ‡ç­¾ã€åˆ†ç»„ |
| æ”¯ä»˜ç³»ç»Ÿ | å¾®ä¿¡/支付宝支付、退款、钱包、转账 |
| å…¬ä¼—号 | ç²‰ä¸ç®¡ç†ã€èœå•、消息、自动回复、素材 |
---
## äºŒã€ç”Ÿäº§ç®¡ç†
生产管理位于 **MES æ¨¡å—** çš„ `pro` åŒ…,覆盖从主计划制定到产品完工入库的完整生产执行流程。
### 2.1 ç”Ÿäº§ä¸»è®¡åˆ’(MPS)
系统搭建从订单到计划的生产指挥体系。支持制定生产主计划(MPS),明确计划生产的产品、数量、计划开始和完成日期,计划可与 ERP é”€å”®è®¢å•关联,实现订单驱动生产。计划支持单独下发和合并下发两种模式——单独下发将单个计划转为独立工单,合并下发将多个计划合并为一个工单组统一排产,适应大批量和小批量的不同生产场景。计划状态覆盖待下发、已下发、已取消的完整生命周期,下发后的计划自动生成生产工单进入执行环节。
- åˆ¶å®šç”Ÿäº§ä¸»è®¡åˆ’,定义产品、数量、计划日期
- æ”¯æŒ**单独下发**和**合并下发**(多计划合并为一个工单组)
- è®¡åˆ’状态跟踪(待下发/已下发/已取消)
- ä¸Ž ERP é”€å”®è®¢å•关联,实现订单驱动生产
### 2.2 ç”Ÿäº§å·¥å•
系统构建完整的生产工单执行框架。支持创建多种类型的生产工单,覆盖自制加工、外协加工、返工返修、拆解工单等制造场景。工单自动关联产品对应的工艺路线和物料清单(BOM),工艺路线定义加工顺序和工序要求,BOM å®šä¹‰æ‰€éœ€ç‰©æ–™åŠç”¨é‡ï¼ŒäºŒè€…联动确保工单执行依据准确完整。工单状态按草稿→已确认→生产中→已完成→已关闭的顺序流转,每个状态转换可配置校验规则。支持父子工单拆分,复杂工单可拆分为多个子工单并行执行。支持自动完成功能,当实际报工数量达到工单计划数量时,工单自动标记为已完成,减少人工操作。
- åˆ›å»ºå’Œç®¡ç†ç”Ÿäº§å·¥å•,关联产品、工艺路线、BOM
- æ”¯æŒå¤šç§å·¥å•类型:**自制加工、外协加工、返工返修、拆解工单** ç­‰
- å·¥å•状态流转:草稿 â†’ å·²ç¡®è®¤ â†’ ç”Ÿäº§ä¸­ â†’ å·²å®Œæˆ â†’ å·²å…³é—­
- æ”¯æŒ**子工单**拆分(父工单关联子工单)
- ç‰©æ–™æ¸…单(工单 BOM)和工艺过程自动从产品主数据带入
- æ”¯æŒ**自动完成**功能(报工量达标后自动完工)
### 2.3 å·¥è‰ºç®¡ç†
系统统一管理生产工艺的基础数据。工艺路线定义产品的完整加工流程,支持多工序串行和并行排布,灵活适配不同的生产组织方式。工序定义明确每道工序的名称、编码、所属工位、标准工时等属性,工序内容可附加详细的作业指导说明,确保一线操作人员执行规范。物料清单(BOM)定义产品在各工序所需的原材料、半成品及用量,支持多层 BOM ç»“构。工艺路线与 BOM ä¹‹é—´å»ºç«‹è”动关系,创建工单时系统自动带入对应的工艺路线和物料清单,避免人工重复配置。
- å·¥è‰ºè·¯çº¿ï¼šå®šä¹‰äº§å“çš„加工顺序,支持多工序串行/并行
- å·¥åºå®šä¹‰ï¼šå®šä¹‰æ¯é“工序的名称、编码、所属工位、标准工时
- å·¥åºå†…容:详细的作业指导内容
- ç‰©æ–™æ¸…单(BOM):定义产品所需物料及用量
- å·¥è‰ºè·¯çº¿ä¸Ž BOM è”动,一键带入工单
### 2.4 ç”Ÿäº§ä»»åŠ¡ï¼ˆæŽ’äº§è°ƒåº¦ï¼‰
系统提供智能化的生产排产调度能力。一键排产功能根据工单的产品、数量、工艺路线自动生成生产任务,按工序拆分为可执行的作业单元。排产前支持预览排产效果,确认排程合理后再执行,避免排产错误影响生产。甘特图以时间轴形式直观展示各任务的时间安排和工序依赖关系,帮助调度人员快速识别冲突和瓶颈。任务状态覆盖待生产→生产中→已完成,系统自动累计排产数量、已产数量、合格数量和不良数量,实时反映生产进度。
- ä¸€é”®æŽ’产:根据工单自动生成生产任务(按工序拆分)
- æŽ’产预览:排产前预览效果,确认后再执行
- ç”˜ç‰¹å›¾ï¼šä»¥ç”˜ç‰¹å›¾å½¢å¼å±•示任务时间线
- ä»»åŠ¡çŠ¶æ€ç®¡ç†ï¼šå¾…ç”Ÿäº§ â†’ ç”Ÿäº§ä¸­ â†’ å·²å®Œæˆ
- è®°å½•排产数量、已产数量、合格数量、不良数量
### 2.5 ç”Ÿäº§æŠ¥å·¥
系统支持灵活的生产完工反馈机制。工人完成生产任务后可通过单个报工、快速报工、批量报工三种模式进行完工登记。报工时记录实际产出数量,区分合格品与不良品,不良品按工废(加工原因)、料废(材料原因)、其他废品三级分类统计,为质量分析和成本核算提供依据。报工完成后系统自动更新工单进度,累计产出接近计划数量时触发完工预警,同时将报工信息推送至 IPQC è¿‡ç¨‹æ£€éªŒçŽ¯èŠ‚ï¼Œé©±åŠ¨è´¨é‡æ£€éªŒæµç¨‹ã€‚
- å·¥äººå¯¹å®Œæˆçš„生产任务进行报工
- æ”¯æŒ**单个报工、快速报工、批量报工**三种模式
- è®°å½•报工数量、合格/不良数量、工废/料废/其他废品数量
- æŠ¥å·¥è§¦å‘ IPQC è¿‡ç¨‹æ£€éªŒã€æ›´æ–°å·¥å•进度
### 2.6 æµè½¬å¡ä¸Žå®‰ç¯
系统提供生产现场的过程追溯与异常管理能力。流转卡(工票)记录产品在各工序间的流转信息,包括每道工序的加工人员、加工时间、设备、投料信息等,实现产品制造过程的全流程追溯。安灯系统支撑产线异常呼叫管理,支持配置多种异常类型(如设备故障、物料短缺、质量问题等),产线人员遇到异常时通过安灯呼叫,系统记录呼叫时间、工位、异常类型和处理状态,逐级上报直至问题解决,减少产线停机时间。
- æµè½¬å¡ï¼ˆå·¥ç¥¨ï¼‰ï¼šè®°å½•产品在各工序间的流转信息
- å®‰ç¯ç³»ç»Ÿï¼šäº§çº¿å¼‚常呼叫管理,支持多种异常类型配置
- å¼‚常记录:记录安灯呼叫的时间、工位、异常类型、处理状态
### 2.7 ç”Ÿäº§è®°å½•
系统自动归档生产过程的全部操作记录。工单从创建、确认、排产、报工到完工的每个操作节点均自动生成操作日志,记录操作人、操作时间、操作内容和结果。同时支持生产过程参数记录,可将工艺参数的实测值(如温度、压力、速度等)与工单关联,为工艺优化和质量追溯积累数据。
- è‡ªåŠ¨è®°å½•å·¥å•æ“ä½œæ—¥å¿—ï¼ˆåˆ›å»ºã€ç¡®è®¤ã€æŽ’äº§ã€æŠ¥å·¥ã€å®Œæˆç­‰ï¼‰
- æ”¯æŒç”Ÿäº§å‚数记录(工艺参数实测值)
---
## ä¸‰ã€è´¨é‡ç®¡ç†
质量管理位于 **MES æ¨¡å—** çš„ `qc` åŒ…,构建 IQC→IPQC→OQC→RQC å…¨ç¨‹è´¨é‡æŽ§åˆ¶ä½“系,确保从来料到出货的每个环节质量可控。
### 3.1 æ¥æ–™æ£€éªŒï¼ˆIQC)
系统建立严格的供应商来料质量把关机制。来料检验(IQC)与仓储到货通知单联动,供应商送货后自动生成待检任务,检验人员依据质检方案对来料进行抽样或全检。记录接收数量、实际检测数量、合格数量和不合格数量,按致命、严重、轻微三级分类统计缺陷数量和缺陷率,为供应商质量评价提供量化依据。检验结论支持合格接收、让步接收(特采)、不合格退货、不合格报废四种处理方式,系统根据结论自动驱动后续的入库或退货流程,确保不合格物料不流入生产环节。
- å¯¹ä¾›åº”商来料进行质量检验
- å…³è”到货通知单,检验合格后方可入库
- è®°å½•接收数量、检测数量、合格/不合格数量
- æŒ‰è‡´å‘½/严重/轻微三级记录缺陷数量和缺陷率
- æ£€éªŒç»“论:合格接收 / è®©æ­¥æŽ¥æ”¶ï¼ˆç‰¹é‡‡ï¼‰/ ä¸åˆæ ¼é€€è´§ / ä¸åˆæ ¼æŠ¥åºŸ
### 3.2 è¿‡ç¨‹æ£€éªŒï¼ˆIPQC)
系统嵌入生产流程的质量过程控制节点。过程检验(IPQC)与生产工单、任务、工位、工序关联,生产报工完成后自动生成待检任务,检验人员在生产现场对在制品进行质量检验。记录工废(加工不当导致的废品)、料废(材料本身缺陷导致的废品)、其他废品数量,精确区分废品责任归属。IPQC ç»“果实时反馈至生产环节,出现质量异常时可触发安灯报警或暂停生产,防止批量不合格品产生。
- å¯¹ç”Ÿäº§è¿‡ç¨‹ä¸­çš„产品进行质量检验
- å…³è”生产工单、生产任务、工位、工序
- è®°å½•工废/料废/其他废品数量
- æŠ¥å·¥å®ŒæˆåŽè‡ªåŠ¨è§¦å‘å¾…æ£€
### 3.3 å‡ºè´§æ£€éªŒï¼ˆOQC)
系统在发货环节设置最终质量放行关卡。出货检验(OQC)在成品发货前执行,关联客户和销售订单,检验合格后方可出库交付客户。检验数据同步至客户质量档案,为客户投诉分析积累基础数据。
- äº§å“å‘货前的最终质量检验
- å…³è”客户,检验合格后方可发货
### 3.4 é€€è´§æ£€éªŒï¼ˆRQC)
系统建立客户退货的规范处理流程。退货检验(RQC)对客户退回产品进行质量确认,判定退货原因和责任归属,根据检验结果决定退货入库、报废或返工处理,检验结论纳入客户投诉和供应商索赔依据。
- å®¢æˆ·é€€è´§äº§å“çš„质量检验
- ç¡®å®šé€€è´§å¤„理方式(退货入库、报废等)
### 3.5 è´¨æ£€æ–¹æ¡ˆç®¡ç†
系统提供可复用的标准化质检方案体系。质检模板统一定义检验标准和规则,支持按来料检验、过程检验、出货检验、退货检验等不同场景分设模板。每个模板下配置检验指标(具体检验项目和指标值)和检验项目(模板下的具体检验项)。模板可绑定到物料、产品、供应商等维度,检验时系统自动匹配对应模板,保证检验标准的统一性和执行的一致性。
- è´¨æ£€æ¨¡æ¿ï¼šå®šä¹‰æ£€éªŒæ ‡å‡†å’Œæ–¹æ¡ˆï¼Œå¯å¤ç”¨äºŽ IQC/IPQC/OQC/RQC
- æ£€éªŒæŒ‡æ ‡ï¼šå®šä¹‰å…·ä½“检验项目和指标值
- æ£€éªŒé¡¹ç›®ï¼šæ¨¡æ¿ä¸‹çš„具体检验项
### 3.6 ç¼ºé™·ç®¡ç†
系统建立统一的缺陷分类与记录体系。缺陷类型定义支持按企业实际产品特点建立多级缺陷分类,覆盖外观缺陷、尺寸缺陷、性能缺陷、功能缺陷等类别。缺陷等级按致命、严重、轻微三级划分,致命缺陷影响产品安全或功能,严重缺陷影响产品使用体验,轻微缺陷不影响使用的瑕疵。缺陷记录在检验过程中实时登记,记录缺陷类型、等级、数量、发现工位、责任归属等信息,为缺陷分析和质量改进积累数据。
- ç¼ºé™·ç±»åž‹ï¼šå®šä¹‰ç¼ºé™·åˆ†ç±»ä½“ç³»
- ç¼ºé™·è®°å½•:记录检验中发现的缺陷详情
- ç¼ºé™·ç­‰çº§ï¼šè‡´å‘½ï¼ˆCRITICAL)/ ä¸¥é‡ï¼ˆMAJOR)/ è½»å¾®ï¼ˆMINOR)
### 3.7 è´¨é‡æŒ‡æ ‡
系统建立量化的质量绩效评估机制。质量指标定义涵盖合格率、不良率、报废率、退货率等常用 KPI,支持企业自定义扩展。指标结果按日、周、月、年等时间维度自动汇总计算,生成趋势图表,管理层可直观掌握质量水平变化趋势,及时识别质量异常波动并采取改进措施。
- å®šä¹‰è´¨é‡ KPI æŒ‡æ ‡ï¼ˆåˆæ ¼çŽ‡ã€ä¸è‰¯çŽ‡ã€æŠ¥åºŸçŽ‡ç­‰ï¼‰
- è®°å½•指标结果及明细数据
- æ”¯æŒæŒ‰æ—¶é—´ç»´åº¦çš„趋势分析
---
## å››ã€è®¾å¤‡ç®¡ç†
设备管理位于 **MES æ¨¡å—** çš„ `dv` åŒ…,覆盖设备从建档登记、日常点检、定期保养到故障维修、报废处置的完整生命周期。
### 4.1 è®¾å¤‡å°è´¦
系统建立企业设备资产的全量档案。设备台账登记每台设备的编码、名称、品牌、规格型号、所属车间、购置日期等基本信息,配合设备类型分类实现按类管理。设备状态实时跟踪运行中、停机、维修中、已报废四种状态,为生产排产提供设备可用性依据。支持 Excel æ‰¹é‡å¯¼å…¥å¯¼å‡ºï¼Œæ–¹ä¾¿è®¾å¤‡ä¿¡æ¯åˆå§‹åŒ–及数据备份。
- è®¾å¤‡æ¡£æ¡ˆç®¡ç†ï¼šè®¾å¤‡ç¼–码、名称、品牌、规格型号、所属车间
- è®¾å¤‡ç±»åž‹åˆ†ç±»ç®¡ç†
- è®¾å¤‡çŠ¶æ€è·Ÿè¸ªï¼šè¿è¡Œä¸­ / åœæœº / ç»´ä¿®ä¸­ / å·²æŠ¥åºŸ
- æ”¯æŒ Excel å¯¼å…¥å¯¼å‡º
### 4.2 ç‚¹æ£€ç®¡ç†
系统建立设备日常点检的规范化管理流程。点检方案定义检查项目清单、检查周期(按日/按周/按月)和关联设备,方案可灵活启用和停用。点检记录登记每次点检的实际执行结果,与方案中的检查项目逐一对应,记录检查值、判定结果和异常说明。点检数据为设备状态评估和预防性维护提供依据。
- ç‚¹æ£€æ–¹æ¡ˆï¼šå®šä¹‰ç‚¹æ£€è®¡åˆ’(检查项目、周期、关联设备)
- å‘¨æœŸç±»åž‹æ”¯æŒï¼šæŒ‰æ—¥/按周/按月
- æ–¹æ¡ˆçŠ¶æ€ç®¡ç†ï¼šå¯ç”¨/停用
- ç‚¹æ£€è®°å½•:记录每次点检的执行结果
### 4.3 ä¿å…»ç®¡ç†
系统提供设备定期保养的计划与执行管理。保养方案定义保养项目、保养周期和关联设备,系统根据设备的最近保养时间和保养周期自动计算下次保养日期,到期前触发提醒通知,避免保养遗漏。保养记录登记每次保养的执行时间、执行人、保养内容和结果,形成设备保养档案,为设备寿命管理和故障预防提供数据支撑。
- ä¿å…»æ–¹æ¡ˆï¼šå®šä¹‰ä¿å…»è®¡åˆ’(保养项目、周期)
- ä¿å…»è®°å½•:记录每次保养的执行结果
- ä¿å…»åˆ°æœŸæé†’(基于设备的最近保养时间和保养周期)
### 4.4 ç»´ä¿®ç®¡ç†
系统构建设备故障维修的标准化工单流程。维修工单从创建到关闭经历五个状态:草稿(填报故障信息)→维修中(维修人员接单处理)→待验收(维修完成等待确认)→已确认(验收通过)→已关闭(归档)。每个状态记录对应的处理人、处理时间、处理内容,形成完整的维修过程追溯链。维修工单关联设备档案,自动累计每台设备的维修次数和维修时长,为设备 MTBF(平均故障间隔时间)和 MTTR(平均修复时间)分析提供基础数据。
- ç»´ä¿®å·¥å•:设备故障报修、维修过程跟踪
- çŠ¶æ€æµè½¬ï¼šè‰ç¨¿ â†’ ç»´ä¿®ä¸­ â†’ å¾…验收 â†’ å·²ç¡®è®¤ â†’ å·²å…³é—­
- è®°å½•报修日期、完成日期、验收日期
- å…³è”设备、维修人员、验收人员
### 4.5 æ£€æŸ¥é¡¹ç›®ä¸Žå·¥å…·ç®¡ç†
系统建立设备检查项目标准库,定义点检和保养的检查项目内容、检查方法和判定标准,项目库可复用于多个点检保养方案,确保检查标准统一。工具管理覆盖工具台账登记(品牌、规格、型号、数量、可用数量)、工具类型分类和工具保养维护记录,形成与设备管理配套的工装管理体系。
- æ£€æŸ¥é¡¹ç›®ï¼šå®šä¹‰è®¾å¤‡ç‚¹æ£€/保养的检查项目库,可复用于多个方案
- å·¥å…·å°è´¦ï¼šç®¡ç†å·¥å…·å“ç‰Œã€è§„格、型号、数量、可用数量
- å·¥å…·ç±»åž‹ï¼šå·¥å…·åˆ†ç±»ç®¡ç†
- å·¥å…·ä¿å…»ï¼šå·¥å…·ä¿å…»ç»´æŠ¤è®°å½•
---
## äº”、决策支持
> **说明**:决策支持模块当前项目中尚未作为独立模块存在,以下内容根据项目已有的数据基础、统计能力和业务需求设计,作为建设方案参考。
### 5.1 æ¨¡å—定位
决策支持模块(Decision Support System)定位为制造运营数据中台,汇聚各业务模块(MES、ERP、CRM、HRM)的生产、质量、设备、销售、采购、人力等全域数据,通过统一的数据模型和分析引擎,为不同层级的管理者提供角色化的决策看板和 KPI ç›‘控预警能力。与各模块内部的统计分析不同,决策支持模块的核心价值在于跨域关联分析——例如将设备停机数据与生产达成率关联、将供应商来料质量与采购成本关联,发现单一模块无法洞察的管理问题。
### 5.2 æ•°æ®åŸºç¡€
项目已具备可直接利用的数据和分析能力:
| æ•°æ®åŸŸ | å·²æœ‰ç»Ÿè®¡èƒ½åŠ› | æ‰€åœ¨æ¨¡å— |
|--------|-------------|----------|
| ç”Ÿäº§åŸŸ | å·¥å•状态分布、产量趋势、良品率趋势 | MES é¦–页看板 |
| é”€å”®åŸŸ | é”€å”®æ¼æ–—、员工业绩、客户画像、排行榜 | CRM ç»Ÿè®¡æ¨¡å— |
| é‡‡è´­/销售域 | é‡‡è´­é‡‘额统计、销售金额统计(今日/本月/今年) | ERP ç»Ÿè®¡ |
| æŠ¥è¡¨å±•示 | ç§¯æœ¨æŠ¥è¡¨å¼•擎 + GoView å¤§å±è®¾è®¡å™¨ | yudao-module-report |
### 5.3 å»ºè®®åŠŸèƒ½æž¶æž„
#### 5.3.1 ç”Ÿäº§å†³ç­–看板
系统整合工单、排产、报工数据,提供生产运营全景视图。生产综合看板实时展示在制工单数量、产量进度、计划达成率和产线稼动率,帮助生产主管快速掌握当前生产状态。计划达成率分析按日、周、月维度对比 MPS è®¡åˆ’数量与实际完工数量,识别计划偏差并追溯原因。产能利用率分析统计各工位、产线的实际产出与理论产能的比率,发现产能瓶颈和闲置资源。生产周期分析追踪工单从创建到完工的平均时长及趋势,为交期承诺和产能规划提供依据。不良品分析按产品、工序、工位维度排序不良品率,辅助定位质量问题高发环节。工时效率分析对比标准工时与实际工时的差异,评估人员和产线效率。
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| ç”Ÿäº§ç»¼åˆçœ‹æ¿ | MES pro | å®žæ—¶å·¥å•状态、产量进度、达成率、产线稼动率 |
| è®¡åˆ’达成率分析 | MES mps/pro | MPS è®¡åˆ’数量 vs å®žé™…完工数量,按日/周/月统计 |
| äº§èƒ½åˆ©ç”¨çއ | MES task/feedback | å„工位/产线的产能利用率、空闲率 |
| ç”Ÿäº§å‘¨æœŸåˆ†æž | MES workorder/feedback | å·¥å•从创建到完工的平均周期、趋势 |
| ä¸è‰¯å“åˆ†æž | MES qc/feedback | æŒ‰äº§å“/工序/工位的不良品率排行和趋势 |
| å·¥æ—¶æ•ˆçŽ‡åˆ†æž | MES feedback | æ ‡å‡†å·¥æ—¶ vs å®žé™…工时对比,效率排行 |
#### 5.3.2 è´¨é‡å†³ç­–看板
系统汇聚 IQC、IPQC、OQC、RQC å…¨é“¾è·¯è´¨é‡æ•°æ®ï¼Œæä¾›è´¨é‡ç®¡ç†é©¾é©¶èˆ±ã€‚质量综合看板同时展示来料、过程、出货三个环节的合格率,一眼看清整体质量水位。供应商质量分析按供应商统计来料合格率,结合到货批次、退货批次数生成供应商质量排名和趋势,为供应商评估和淘汰提供数据支持。工序质量分析呈现各工序 IPQC æ£€éªŒçš„合格率和不良品分布,识别过程质量薄弱环节。缺陷 Pareto åˆ†æžåŸºäºŽç¼ºé™·è®°å½•生成帕累托图(80/20 åˆ†æžï¼‰ï¼Œå¸®åŠ©è´¨é‡å·¥ç¨‹å¸ˆèšç„¦å½±å“æœ€å¤§çš„å°‘æ•°ç¼ºé™·ç±»åž‹ã€‚è´¨é‡æˆæœ¬åˆ†æžå…³è”æŠ¥åºŸæ•°é‡ã€è¿”å·¥å·¥æ—¶å’Œç‰©æ–™æˆæœ¬ï¼Œé‡åŒ–è´¨é‡é—®é¢˜çš„ç»æµŽå½±å“ã€‚å®¢æˆ·æŠ•è¯‰åˆ†æžæ±‡æ€»é€€è´§æ£€éªŒæ•°æ®å’Œå®¢æˆ·æŠ•è¯‰è®°å½•ï¼Œè¿½è¸ªæŠ•è¯‰å¤„ç†æ—¶æ•ˆå’Œé—­çŽ¯çŽ‡ã€‚
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| è´¨é‡ç»¼åˆçœ‹æ¿ | MES qc | æ¥æ–™/过程/出货合格率总览 |
| ä¾›åº”商质量分析 | MES qc/iqc | æŒ‰ä¾›åº”商统计来料合格率,排名和趋势 |
| å·¥åºè´¨é‡åˆ†æž | MES qc/ipqc | å„工序过程检验的合格率、不良品分布 |
| ç¼ºé™· Pareto åˆ†æž | MES qc/defect | ç¼ºé™·ç±»åž‹çš„帕累托图(80/20 åˆ†æžï¼‰ |
| è´¨é‡æˆæœ¬åˆ†æž | MES qc + ERP | æŠ¥åºŸæˆæœ¬ã€è¿”工成本统计 |
| å®¢æˆ·æŠ•诉分析 | MES qc/rqc | é€€è´§æ£€éªŒå’Œå®¢æˆ·æŠ•诉趋势 |
#### 5.3.3 è®¾å¤‡å†³ç­–看板
系统整合设备台账、点检保养记录和维修工单数据,构建设备管理决策视图。设备综合看板展示设备总数、运行率、停机率和维修中设备数量,帮助设备主管一目了然掌握设备整体状态。OEE ç»¼åˆæ•ˆçŽ‡åˆ†æžæŒ‰è®¾å¤‡æˆ–äº§çº¿è®¡ç®—ç»¼åˆæ•ˆçŽ‡ï¼ˆå¯ç”¨çŽ‡ Ã— æ€§èƒ½çއ Ã— è´¨é‡çŽ‡ï¼‰ï¼Œè¯†åˆ«ä½Žæ•ˆè®¾å¤‡å¹¶åˆ†æžæŸå¤±æ¥æºã€‚MTBF/MTTR åˆ†æžè¿½è¸ªå¹³å‡æ•…障间隔时间和平均修复时间的变化趋势,评估设备可靠性和维修效率。维修成本分析统计各设备的维修费用,包含备件成本、人工成本和停机损失,为设备更新决策提供经济依据。点检保养执行率统计各周期内点检保养计划的完成率,监控预防性维护的落地执行情况。
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| è®¾å¤‡ç»¼åˆçœ‹æ¿ | MES dv | è®¾å¤‡æ€»æ•°ã€è¿è¡ŒçŽ‡ã€åœæœºçŽ‡ã€ç»´ä¿®ä¸­è®¾å¤‡ |
| OEE ç»¼åˆæ•ˆçއ | MES dv + pro | è®¾å¤‡ç»¼åˆæ•ˆçŽ‡ï¼ˆå¯ç”¨çŽ‡ Ã— æ€§èƒ½çއ Ã— è´¨é‡çŽ‡ï¼‰ |
| MTBF/MTTR åˆ†æž | MES dv/repair | å¹³å‡æ•…障间隔时间、平均修复时间趋势 |
| ç»´ä¿®æˆæœ¬åˆ†æž | MES dv/repair | å„设备维修费用统计和趋势 |
| ç‚¹æ£€ä¿å…»æ‰§è¡Œçއ | MES dv/check | ç‚¹æ£€ä¿å…»è®¡åˆ’执行率 |
| è®¾å¤‡çŠ¶æ€åˆ†å¸ƒ | MES dv/machinery | æŒ‰è½¦é—´/类型的设备状态分布 |
#### 5.3.4 é”€å”®å†³ç­–看板
系统整合 CRM å®¢æˆ·ã€å•†æœºã€åˆåŒã€å›žæ¬¾æ•°æ®ï¼Œæä¾›é”€å”®ç»è¥åˆ†æžè§†è§’。销售综合看板按月度、季度、年度展示销售额、达成率和同比环比变化,支撑经营分析会议。销售漏斗监控实时跟踪各阶段商机的数量和金额转化,对长期停滞的商机触发预警,帮助销售管理者及时干预。客户价值分析基于 RFM æ¨¡åž‹ï¼ˆæœ€è¿‘购买时间、购买频率、购买金额)对客户分层,识别高价值客户和流失风险客户。产品销量分析按产品、品类维度展示销量排行和时间趋势,指导产品策略调整。回款监控实时展示应收账款全景——已收金额、待收金额、逾期金额,按账龄分段统计,帮助财务和销售协同推进回款。
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| é”€å”®ç»¼åˆçœ‹æ¿ | CRM statistics | æœˆåº¦/季度/年度销售额、达成率、同比环比 |
| é”€å”®æ¼æ–—监控 | CRM statistics-funnel | å„阶段商机数量和金额,异常预警 |
| å®¢æˆ·ä»·å€¼åˆ†æž | CRM + ERP | RFM åˆ†æžï¼ˆæœ€è¿‘è´­ä¹°/频率/金额) |
| äº§å“é”€é‡åˆ†æž | CRM + ERP | æŒ‰äº§å“/品类维度的销量排行和趋势 |
| å›žæ¬¾ç›‘控 | CRM receivable | åº”æ”¶/已收/逾期金额,账龄分析 |
#### 5.3.5 é‡‡è´­å†³ç­–看板
系统整合 ERP é‡‡è´­ç»Ÿè®¡å’Œ MES æ¥æ–™æ£€éªŒæ•°æ®ï¼Œæä¾›é‡‡è´­ç®¡ç†å…¨æ™¯åˆ†æžã€‚采购综合看板按月度和年度展示采购金额及趋势,反映采购规模变化。供应商绩效分析将交期达成率与来料质量合格率结合,按供应商生成综合评价得分,支持供应商分级管理。采购成本分析追踪核心物料的价格波动趋势,辅助采购议价和成本控制决策。
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| é‡‡è´­ç»¼åˆçœ‹æ¿ | ERP statistics | æœˆåº¦/年度采购金额、趋势 |
| ä¾›åº”商绩效分析 | ERP + MES qc/iqc | äº¤æœŸè¾¾æˆçއ + è´¨é‡åˆæ ¼çŽ‡ç»¼åˆè¯„çº§ |
| é‡‡è´­æˆæœ¬åˆ†æž | ERP | ç‰©æ–™é‡‡è´­ä»·æ ¼æ³¢åŠ¨è¶‹åŠ¿ |
#### 5.3.6 äººåŠ›å†³ç­–çœ‹æ¿
系统整合 HRM è€ƒå‹¤æ•°æ®å’Œ MES ç”Ÿäº§æ•°æ®ï¼Œæä¾›äººåŠ›èµ„æºæ•ˆèƒ½åˆ†æžã€‚è€ƒå‹¤ç»Ÿè®¡å±•ç¤ºå„éƒ¨é—¨å‡ºå‹¤çŽ‡ã€åŠ ç­æ—¶é•¿è¶‹åŠ¿ï¼Œè¯„ä¼°äººåŠ›æŠ•å…¥è§„æ¨¡ã€‚äººå‡äº§å‡ºæŒ‰éƒ¨é—¨æˆ–è½¦é—´ç»Ÿè®¡äººå‡äº§é‡ã€äººå‡äº§å€¼ï¼Œè¯„ä¼°äººåŠ›æ•ˆèƒ½ã€‚äººå‘˜åˆ†å¸ƒå±•ç¤ºå„éƒ¨é—¨ã€å„è½¦é—´çš„äººå‘˜ç¼–åˆ¶æ•°é‡åŠå˜åŠ¨è¶‹åŠ¿ï¼Œæ”¯æ’‘äººåŠ›è§„åˆ’å†³ç­–ã€‚
| çœ‹æ¿/报表 | æ•°æ®æ¥æº | åŠŸèƒ½è¯´æ˜Ž |
|-----------|----------|----------|
| è€ƒå‹¤ç»Ÿè®¡ | HRM | å‡ºå‹¤çŽ‡ã€åŠ ç­æ—¶é•¿è¶‹åŠ¿ |
| äººå‡äº§å‡º | HRM + MES | äººå‡äº§é‡/产值趋势 |
| äººå‘˜åˆ†å¸ƒ | HRM + MES | å„部门/车间人员数量及变动 |
### 5.4 æŠ€æœ¯å®žçŽ°å»ºè®®
#### 5.4.1 æ¨¡å—结构
```
yudao-module-dss(新建)          # å†³ç­–支持模块
├── controller/admin/dss/
│   â”œâ”€â”€ DssProductionController   # ç”Ÿäº§å†³ç­–看板 API
│   â”œâ”€â”€ DssQualityController      # è´¨é‡å†³ç­–看板 API
│   â”œâ”€â”€ DssDeviceController       # è®¾å¤‡å†³ç­–看板 API
│   â”œâ”€â”€ DssSaleController         # é”€å”®å†³ç­–看板 API
│   â”œâ”€â”€ DssPurchaseController     # é‡‡è´­å†³ç­–看板 API
│   â””── DssKpiController          # KPI æŒ‡æ ‡ç®¡ç† API
├── service/dss/
│   â”œâ”€â”€ DssProductionService      # ç”Ÿäº§å†³ç­–服务
│   â”œâ”€â”€ DssQualityService         # è´¨é‡å†³ç­–服务
│   â”œâ”€â”€ DssDeviceService          # è®¾å¤‡å†³ç­–服务
│   â”œâ”€â”€ DssSaleService            # é”€å”®å†³ç­–服务
│   â””── DssKpiService             # KPI ç®¡ç†æœåŠ¡
└── dal/dataobject/dss/
    â””── DssKpiDefinitionDO        # KPI æŒ‡æ ‡å®šä¹‰è¡¨ï¼ˆå¯é€‰ï¼‰
```
#### 5.4.2 æ•°æ®èŽ·å–æ–¹å¼
- **跨模块 API è°ƒç”¨**:通过各模块的 `*-api` æŽ¥å£èŽ·å–æ•°æ®ï¼ˆå¦‚ CRM å·²æœ‰çš„ `CrmReportApi`),避免直连数据库造成的模块耦合。
- **聚合查询**:复杂看板数据通过 SQL èšåˆæŸ¥è¯¢ç›´æŽ¥ä»Žæ•°æ®åº“获取,遵循项目现有 MES é¦–页看板 `MesHomeStatisticsMapper` çš„实现模式,兼顾灵活性和性能。
- **缓存策略**:看板数据建议使用 Redis ç¼“存,设置合理的刷新周期(如 5 åˆ†é’Ÿï¼‰ï¼Œé¿å…æ¯æ¬¡é¡µé¢è®¿é—®éƒ½è§¦å‘重查询对业务库造成压力。
#### 5.4.3 å‰ç«¯å±•示
- **GoView å¤§å±**:项目已集成 GoView å¤§å±è®¾è®¡å™¨ï¼Œæ”¯æŒæ‹–拽式大屏搭建,可直接用于生产车间看板、总经理驾驶舱等场景。
- **积木报表**:已集成积木报表引擎,支持复杂报表设计、导出和打印,适合月度经营分析报告等固定格式报表。
- **ECharts å›¾è¡¨**:前端已使用 ECharts ä½œä¸ºå›¾è¡¨æ¸²æŸ“引擎(参考 MES é¦–页看板实现),建议各看板统一使用保持风格一致。
### 5.5 å…³é”® KPI æŒ‡æ ‡å»ºè®®
系统可预设以下核心 KPI æŒ‡æ ‡çš„计算逻辑,支持企业自定义扩展:
| æŒ‡æ ‡ç±»åˆ« | æŒ‡æ ‡åç§° | è®¡ç®—公式 |
|----------|----------|----------|
| ç”Ÿäº§ | è®¡åˆ’达成率 | å®žé™…完工数 / è®¡åˆ’æ•° Ã— 100% |
| ç”Ÿäº§ | äº§å“åˆæ ¼çއ | åˆæ ¼æ•° / æ€»ç”Ÿäº§æ•° Ã— 100% |
| ç”Ÿäº§ | äº§èƒ½åˆ©ç”¨çއ | å®žé™…产量 / ç†è®ºäº§èƒ½ Ã— 100% |
| è´¨é‡ | æ¥æ–™åˆæ ¼çއ | æ¥æ–™åˆæ ¼æ‰¹æ¬¡æ•° / æ¥æ–™æ€»æ‰¹æ¬¡æ•° Ã— 100% |
| è´¨é‡ | å·¥åºåˆæ ¼çއ | å·¥åºåˆæ ¼æ•° / å·¥åºæ£€éªŒæ€»æ•° Ã— 100% |
| è´¨é‡ | å®¢æˆ·é€€è´§çއ | é€€è´§æ•° / å‘货总数 Ã— 100% |
| è®¾å¤‡ | OEE | å¯ç”¨çއ Ã— æ€§èƒ½çއ Ã— è´¨é‡çއ |
| è®¾å¤‡ | MTBF | æ€»è¿è¡Œæ—¶é—´ / æ•…障次数 |
| è®¾å¤‡ | MTTR | æ€»ç»´ä¿®æ—¶é—´ / æ•…障次数 |
| é”€å”® | é”€å”®è¾¾æˆçއ | å®žé™…销售额 / ç›®æ ‡é”€å”®é¢ Ã— 100% |
| é”€å”® | å›žæ¬¾çއ | å·²å›žæ¬¾é‡‘额 / åº”收金额 Ã— 100% |
---
## å…­ã€è¾…助模块概览
### 6.1 ä¸»æ•°æ®ç®¡ç†ï¼ˆMDM)
系统建立企业级统一主数据中心,为各业务模块提供标准化基础数据。物料主数据统一管理产品、物料分类、品牌、计量单位等信息,确保 MES、ERP、CRM å¼•用同一套物料编码和属性。仓库定义维护仓库、库区、库位的层级结构,为仓储管理模块提供基础数据支撑。BOM ç®¡ç†ç»´æŠ¤äº§å“çš„物料清单结构,作为生产工单物料计算的基准。自动编码规则支持按业务类型、日期、流水号等维度灵活配置单据编码规则,保证各类业务单据编号的规范性和唯一性。
- ç‰©æ–™ä¸»æ•°æ®ï¼šäº§å“ã€ç‰©æ–™åˆ†ç±»ã€å“ç‰Œã€è®¡é‡å•位
- ä»“库定义:仓库、库区、库位
- BOM ç®¡ç†ï¼šäº§å“ç‰©æ–™æ¸…单
- è‡ªåŠ¨ç¼–ç ï¼šä¸šåŠ¡å•æ®ç¼–ç è§„åˆ™é…ç½®
### 6.2 ä»“储管理(WM)
系统构建从到货到发货的完整仓储作业体系。入库管理覆盖到货通知→IQC æ£€éªŒâ†’入库上架的标准流程,到货通知单作为检验和入库的驱动源头。出库管理支持生产领料、销售发货、外协发料、退货出库等多种出库场景,出库单可关联源单(工单、销售订单)以保持业务追溯。库存管理提供物料库存和产品库存的实时查询、库存盘点、库存调拨等能力,库存交易记录完整记录每次出入库操作的物料、数量、批次、库位。追溯管理通过条码、批次号、序列号实现物料和产品从入库到出库的全程追踪,满足质量追溯和召回管理需求。
- å…¥åº“管理:到货通知 â†’ IQC æ£€éªŒ â†’ å…¥åº“
- å‡ºåº“管理:生产领料、销售发货、外协发料、退货出库
- åº“存管理:物料库存、产品库存、库存盘点、库存调拨
- è¿½æº¯ç®¡ç†ï¼šæ¡ç ã€æ‰¹æ¬¡å·ã€åºåˆ—号全程追溯
### 6.3 äººåŠ›èµ„æºç®¡ç†ï¼ˆHRM)
系统提供企业人事与考勤薪酬管理能力。考勤管理覆盖排班计划、打卡记录、加班申请、请假审批等日常考勤场景,考勤数据自动汇总为薪酬计算提供依据。薪酬管理支持薪资结构配置、社保公积金计算、个税计算和工资条生成。人事审批将请假、调岗、离职等流程接入 BPM å·¥ä½œæµå¼•擎,实现标准化审批流转。
- è€ƒå‹¤ç®¡ç†ï¼šæŽ’班、打卡、加班、请假
- è–ªé…¬ç®¡ç†ï¼šå·¥èµ„计算、社保公积金
- äººäº‹å®¡æ‰¹ï¼šè¯·å‡ã€è°ƒå²—、离职等工作流审批
### 6.4 å·¥ä½œæµç®¡ç†ï¼ˆBPM)
系统基于 Flowable 6 å¼•擎构建企业级流程管理平台。流程定义支持在线设计审批流程图和表单布局,覆盖 CRM åˆåŒå®¡æ‰¹ã€å›žæ¬¾å®¡æ‰¹ã€æŠ¥ä»·å•审批等业务场景。审批中心提供我的申请、我的待办、我的已办三个视图,方便员工跟踪申请进度和处理待审批事项。流程引擎与业务数据深度集成,审批通过或驳回后自动更新业务单据状态,实现业务流程自动化闭环。
- åŸºäºŽ Flowable 6 å¼•擎
- æµç¨‹å®šä¹‰ã€è¡¨å•配置
- æˆ‘的申请、我的待办、我的已办
- CRM åˆåŒ/回款/报价单的审批流
### 6.5 å³æ—¶é€šè®¯ï¼ˆIM)
系统内置企业即时通讯能力,支撑内部沟通协作。支持单聊和群聊两种会话模式,消息收发支持文本、图片、文件等多种类型。提供消息已读回执和消息撤回功能,管理者可通过通讯数据看板查看活跃用户趋势、消息量趋势、TOP å‘送者排名等数据,了解内部沟通活跃度。
- å•聊、群聊、消息收发
- æ¶ˆæ¯å·²è¯»ã€æ’¤å›ž
- é€šè®¯æ•°æ®çœ‹æ¿ï¼ˆæ´»è·ƒç”¨æˆ·ã€æ¶ˆæ¯è¶‹åŠ¿ã€TOP å‘送者)
### 6.6 åŸºç¡€è®¾æ–½
系统底层提供完整的企业级基础设施支撑。权限管理覆盖用户、角色、菜单、部门、岗位的完整 RBAC æƒé™ä½“系,支持数据权限按部门、按用户分级控制。数据字典为系统中各类枚举值提供统一的配置和管理入口。文件管理基于 Storage API å’Œ Attachment ä¸­é—´è¡¨æœºåˆ¶ï¼Œç»Ÿä¸€å¤„理各业务模块的文件上传、存储和业务关联。定时任务基于 Quartz è°ƒåº¦æ¡†æž¶ï¼Œæ”¯æŒ Cron è¡¨è¾¾å¼é…ç½®å’Œç®¡ç†åŽå°å¯åœã€‚代码生成器支持根据数据库表结构自动生成 Controller、Service、DAO、VO ç­‰æ ‡å‡†åŒ–代码,减少重复开发工作。
- æƒé™ç®¡ç†ï¼šç”¨æˆ·ã€è§’色、菜单、部门、岗位
- æ•°æ®å­—典:枚举值统一管理
- æ–‡ä»¶ç®¡ç†ï¼šStorage API + Attachment ä¸­é—´è¡¨
- å®šæ—¶ä»»åŠ¡ï¼šQuartz è°ƒåº¦
- ä»£ç ç”Ÿæˆï¼šè‡ªåŠ¨ç”Ÿæˆ CRUD ä»£ç 
---
## ä¸ƒã€æ¨¡å—依赖关系
```
                        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                        â”‚  yudao-module-system â”‚  ç”¨æˆ·/权限/字典
                        â””────────┬─────────┘
                                 â”‚
          â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
          â”‚                      â”‚                      â”‚
  â”Œâ”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”
  â”‚  MDM (主数据) â”‚    â”‚   BPM (工作流)     â”‚   â”‚  Infra (基础设施) â”‚
  â”‚  ç‰©æ–™/BOM/仓库 â”‚    â”‚   æµç¨‹/审批         â”‚   â”‚  æ–‡ä»¶/任务/监控    â”‚
  â””───────┬──────┘    â””───────────────────┘   â””─────────────────┘
          â”‚
    â”Œâ”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
    â”‚     â”‚                                â”‚
┌───▼─────▼──────┐              â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  MES (制造执行)  â”‚              â”‚   ERP (企业资源)      â”‚
│  ç”Ÿäº§/质量/设备   â”‚◄────────────►│   é‡‡è´­/销售/库存/财务   â”‚
│  ä»“储/工具/排班   â”‚              â”‚                      â”‚
└────────┬───────┘              â””──────────┬───────────┘
         â”‚                                 â”‚
         â”‚              â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
         â”‚              â”‚     CRM (客户关系)              â”‚
         â””──────────────►│     çº¿ç´¢/客户/商机/合同/回款     â”‚
                         â””──────────────────────────────┘
```
数据流向:
- **MDM** æä¾›ç‰©æ–™ã€BOM、仓库等主数据 â†’ MES + ERP
- **MES** ç”Ÿäº§æ‰§è¡Œ â†’ è´¨æ£€ç»“果影响 ERP å…¥åº“/发货决策
- **ERP** é”€å”®è®¢å• â†’ MES ç”Ÿäº§è®¡åˆ’驱动;采购订单 â†’ MES æ¥æ–™æ£€éªŒ
- **CRM** åˆåŒç­¾çº¦ â†’ ERP é”€å”®è®¢å•生成
- **BPM** ä¸º CRM åˆåŒ/回款提供审批流支持
- **决策支持(DSS)** æ•´åˆä»¥ä¸Šæ‰€æœ‰æ¨¡å—数据,提供跨域分析
---
## å…«ã€æ€»ç»“
本项目已构建了一套完整的制造运营管理能力:
| é¢†åŸŸ | è¦†ç›–能力 | æˆç†Ÿåº¦ |
|------|----------|--------|
| è¥é”€ç®¡ç† | çº¿ç´¢â†’客户→商机→报价→合同→回款 å…¨æµç¨‹ | **完整** |
| ç”Ÿäº§ç®¡ç† | MPS→工单→排产→报工→完工 å…¨æµç¨‹ | **完整** |
| è´¨é‡ç®¡ç† | IQC + IPQC + OQC + RQC å…¨ç¨‹è´¨é‡æŽ§åˆ¶ | **完整** |
| è®¾å¤‡ç®¡ç† | å°è´¦â†’点检→保养→维修 å…¨ç”Ÿå‘½å‘¨æœŸ | **完整** |
| å†³ç­–支持 | å„模块分散统计,缺少统一决策看板 | **待建设** |
建议下一步优先建设**决策支持模块**,打通各业务域的数据孤岛,为管理层提供一站式经营决策看板。
pom.xml
@@ -35,6 +35,9 @@
        <!-- HRM äººåŠ›èµ„æºç®¡ç†æ¨¡å— -->
        <module>yudao-module-hrm-api</module>
        <module>yudao-module-hrm</module>
        <!-- SRM ä¾›åº”商关系管理模块 -->
        <module>yudao-module-srm-api</module>
        <module>yudao-module-srm</module>
        <!-- è¯·å‚考 https://doc.iocoder.cn/ai/build/ æ–‡æ¡£ï¼Œå®Œæˆ AI æ¨¡å—的启动!!! -->
<!--        <module>yudao-module-ai</module>-->
    </modules>
sql/mysql/hrm_user_handover_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
-- HRM用户数据交接映射表
CREATE TABLE IF NOT EXISTS `hrm_user_handover` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '映射ID',
    `from_user_id` bigint NOT NULL COMMENT '离职用户ID',
    `to_user_id` bigint NOT NULL COMMENT '交接目标用户ID',
    `resignation_id` bigint NOT NULL COMMENT '关联的离职申请ID',
    `handover_status` tinyint NOT NULL DEFAULT 0 COMMENT '交接状态:0-进行中,20-已完成',
    `handover_time` datetime DEFAULT NULL COMMENT '交接完成时间',
    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uk_from_to_resignation` (`from_user_id`, `to_user_id`, `resignation_id`),
    KEY `idx_from_user_id` (`from_user_id`),
    KEY `idx_to_user_id` (`to_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM用户数据交接映射表';
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateDeserializer.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.common.util.json.databind;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
 * åŸºäºŽæ—¶é—´æˆ³çš„ LocalDate ååºåˆ—化器
 * å…¼å®¹ï¼šæ•°å­—时间戳、字符串时间戳、日期字符串
 *
 * @author è€äº”
 */
public class TimestampLocalDateDeserializer extends ValueDeserializer<LocalDate> {
    public static final TimestampLocalDateDeserializer INSTANCE = new TimestampLocalDateDeserializer();
    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    @Override
    public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
        if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) {
            return LocalDate.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
        }
        String text = p.getValueAsString();
        try {
            long millis = Long.parseLong(text);
            return LocalDate.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
        } catch (NumberFormatException e) {
            // ä¸æ˜¯çº¯æ•°å­—,按日期格式解析
        }
        return LocalDate.parse(text, DEFAULT_FORMATTER);
    }
}
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
@@ -2,12 +2,14 @@
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
 * åŸºäºŽæ—¶é—´æˆ³çš„ LocalDateTime ååºåˆ—化器
@@ -18,10 +20,23 @@
    public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
        // å°† Long æ—¶é—´æˆ³ï¼Œè½¬æ¢ä¸º LocalDateTime å¯¹è±¡
        // æ•°å­—类型:作为时间戳处理
        if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
    }
        // å­—符串类型:先尝试作为时间戳解析,再按日期格式解析
        String text = p.getValueAsString();
        try {
            long millis = Long.parseLong(text);
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
        } catch (NumberFormatException e) {
            // ä¸æ˜¯çº¯æ•°å­—,按默认日期格式解析
        }
        return LocalDateTime.parse(text, DEFAULT_FORMATTER);
    }
}
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java
@@ -35,9 +35,11 @@
        super(LocalDateTime.class);
    }
    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
        // æƒ…况一:有 JsonFormat è‡ªå®šä¹‰æ³¨è§£ï¼Œåˆ™ä½¿ç”¨å®ƒã€‚https://github.com/YunaiV/ruoyi-vue-pro/pull/1019
        // æƒ…况一:有 JsonFormat è‡ªå®šä¹‰æ³¨è§£ï¼Œåˆ™ä½¿ç”¨å®ƒ
        String fieldName = gen.streamWriteContext().currentName();
        if (fieldName != null) {
            Object currentValue = gen.currentValue();
@@ -45,23 +47,30 @@
                Class<?> clazz = currentValue.getClass();
                Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, this::buildFieldMap);
                Field field = fieldMap.get(fieldName);
                // è¿›ä¸€æ­¥ä¿®å¤ï¼šhttps://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1480
                if (field != null && field.isAnnotationPresent(JsonFormat.class)) {
                    JsonFormat jsonFormat = field.getAnnotation(JsonFormat.class);
                    // è‹¥æŒ‡å®šè¾“出为数字(时间戳),则按原始方式处理
                    if (jsonFormat.shape() == JsonFormat.Shape.NUMBER) {
                        gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
                        return;
                    }
                    // è‹¥æŒ‡å®šäº†è‡ªå®šä¹‰ pattern,则使用 pattern æ ¼å¼åŒ–
                    if (StrUtil.isNotEmpty(jsonFormat.pattern())) {
                    try {
                        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(jsonFormat.pattern());
                        gen.writeString(formatter.format(value));
                        return;
                    } catch (Exception ex) {
                        log.warn("[serialize][({}#{}) ä½¿ç”¨ JsonFormat pattern å¤±è´¥ï¼Œå°è¯•使用默认的 Long æ—¶é—´æˆ³]",
                            log.warn("[serialize][({}#{}) ä½¿ç”¨ JsonFormat pattern å¤±è´¥ï¼Œå°è¯•使用默认格式]",
                                clazz.getName(), fieldName, ex);
                    }
                }
            }
        }
        }
        // æƒ…况二:默认将 LocalDateTime å¯¹è±¡ï¼Œè½¬æ¢ä¸º Long æ—¶é—´æˆ³
        gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        // æƒ…况二:默认格式化为 yyyy-MM-dd HH:mm:ss å­—符串
        gen.writeString(DEFAULT_FORMATTER.format(value));
    }
    /**
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/config/YudaoJacksonAutoConfiguration.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
@@ -11,7 +12,6 @@
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ext.javatime.deser.LocalDateDeserializer;
import tools.jackson.databind.ext.javatime.deser.LocalTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateSerializer;
import tools.jackson.databind.ext.javatime.ser.LocalTimeSerializer;
@@ -44,7 +44,7 @@
        m.addSerializer(Long.TYPE, NumberSerializer.INSTANCE);
        // LocalDate / LocalTime
        m.addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE);
        m.addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE);
        m.addDeserializer(LocalDate.class, TimestampLocalDateDeserializer.INSTANCE);
        m.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
        m.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
        // LocalDateTime < - > EpochMillis
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.erp.api.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
@@ -75,4 +76,12 @@
     */
    CommonResult<Boolean> updateOrderInStatus(Long orderId);
    /**
     * åˆ›å»ºé‡‡è´­è®¢å•(RPC è°ƒç”¨ï¼‰
     *
     * @param createReqDTO åˆ›å»ºä¿¡æ¯
     * @return è®¢å•编号
     */
    CommonResult<Long> createPurchaseOrder(ErpPurchaseOrderCreateReqDTO createReqDTO);
}
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderCreateReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.erp.api.purchase.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "RPC æœåŠ¡ - ERP é‡‡è´­è®¢å•创建 Request DTO")
@Data
public class ErpPurchaseOrderCreateReqDTO {
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long supplierId;
    @Schema(description = "结算账户编号", example = "1")
    private Long accountId;
    @Schema(description = "下单时间")
    private LocalDateTime orderTime;
    @Schema(description = "优惠率,百分比", example = "10")
    private BigDecimal discountPercent;
    @Schema(description = "定金金额,单位:元", example = "100")
    private BigDecimal depositPrice;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
    @Schema(description = "附件 blobId åˆ—表")
    private List<Long> blobIds;
    @Schema(description = "订单项列表")
    private List<Item> items;
    @Schema(description = "订单项")
    @Data
    public static class Item {
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Long productId;
        @Schema(description = "产品单位编号", example = "1")
        private Long productUnitId;
        @Schema(description = "产品单价,单位:元", example = "100")
        private BigDecimal productPrice;
        @Schema(description = "数量", example = "10")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "10")
        private BigDecimal taxPercent;
        @Schema(description = "备注", example = "备注")
        private String remark;
        @Schema(description = "是否需要来料检验", example = "false")
        private Boolean qcCheckFlag;
    }
}
yudao-module-erp/pom.xml
@@ -45,6 +45,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- SRM ä¾›åº”商关系管理 API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-srm-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- MDM ä¸»æ•°æ®ç®¡ç† API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
@@ -2,8 +2,10 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
@@ -107,4 +109,13 @@
        return success(true);
    }
    @Override
    public CommonResult<Long> createPurchaseOrder(ErpPurchaseOrderCreateReqDTO createReqDTO) {
        ErpPurchaseOrderSaveReqVO saveReqVO = BeanUtils.toBean(createReqDTO, ErpPurchaseOrderSaveReqVO.class);
        if (createReqDTO.getItems() != null) {
            saveReqVO.setItems(BeanUtils.toBean(createReqDTO.getItems(), ErpPurchaseOrderSaveReqVO.Item.class));
        }
        return success(purchaseOrderService.createPurchaseOrder(saveReqVO));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java
@@ -15,10 +15,10 @@
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -50,7 +50,7 @@
    @Resource
    private ErpFinancePaymentService financePaymentService;
    @Resource
    private ErpSupplierService supplierService;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private ErpAccountService accountService;
@@ -136,8 +136,9 @@
                convertSet(pageResult.getList(), ErpFinancePaymentDO::getId));
        Map<Long, List<ErpFinancePaymentItemDO>> financePaymentItemMap = convertMultiMap(paymentItemList, ErpFinancePaymentItemDO::getPaymentId);
        // 1.2 ä¾›åº”商信息
        Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
                convertSet(pageResult.getList(), ErpFinancePaymentDO::getSupplierId));
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(pageResult.getList(), ErpFinancePaymentDO::getSupplierId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 1.3 ç»“算账户信息
        Map<Long, ErpAccountDO> accountMap = accountService.getAccountMap(
                convertSet(pageResult.getList(), ErpFinancePaymentDO::getAccountId));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -13,9 +13,9 @@
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
@@ -51,7 +51,7 @@
    @Resource
    private ErpPurchaseOrderService purchaseOrderService;
    @Resource
    private ErpSupplierService supplierService;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MdmItemApi mdmItemApi;
@@ -171,8 +171,9 @@
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductId));
        // 1.3 ä¾›åº”商信息
        Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
                convertSet(pageResult.getList(), ErpPurchaseOrderDO::getSupplierId));
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(pageResult.getList(), ErpPurchaseOrderDO::getSupplierId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 1.4 ç®¡ç†å‘˜ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), purchaseOrder -> Long.parseLong(purchaseOrder.getCreator())));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -13,9 +13,9 @@
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -52,7 +52,7 @@
    @Resource
    private ErpPurchaseRequestService purchaseRequestService;
    @Resource
    private ErpSupplierService supplierService;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MdmItemApi mdmItemApi;
@@ -162,8 +162,9 @@
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
        // 1.3 ä¾›åº”商信息
        Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getSupplierId));
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getSupplierId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 1.4 ç”¨æˆ·ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestUserId));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java
@@ -3,6 +3,8 @@
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -37,6 +39,9 @@
    @Resource
    private ErpSupplierService supplierService;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @PostMapping("/create")
    @Operation(summary = "创建供应商")
@@ -82,7 +87,7 @@
    @GetMapping("/simple-list")
    @Operation(summary = "获得供应商精简列表", description = "只包含被开启的供应商,主要用于前端的下拉选项")
    public CommonResult<List<ErpSupplierRespVO>> getSupplierSimpleList() {
        List<ErpSupplierDO> list = supplierService.getSupplierListByStatus(CommonStatusEnum.ENABLE.getStatus());
        List<SrmSupplierRespDTO> list = srmSupplierApi.getSupplierSimpleList().getCheckedData();
        return success(convertList(list, supplier -> new ErpSupplierRespVO().setId(supplier.getId()).setName(supplier.getName())));
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java
@@ -8,6 +8,8 @@
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpSupplierMapper;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -31,6 +33,9 @@
    @Resource
    private ErpSupplierMapper supplierMapper;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Override
    public Long createSupplier(ErpSupplierSaveReqVO createReqVO) {
@@ -86,14 +91,14 @@
    @Override
    public ErpSupplierDO validateSupplier(Long id) {
        ErpSupplierDO supplier = supplierMapper.selectById(id);
        SrmSupplierRespDTO supplier = srmSupplierApi.getSupplier(id).getCheckedData();
        if (supplier == null) {
            throw exception(SUPPLIER_NOT_EXISTS);
        }
        if (CommonStatusEnum.isDisable(supplier.getStatus())) {
            throw exception(SUPPLIER_NOT_ENABLE, supplier.getName());
        }
        return supplier;
        return null;
    }
    @Override
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/api/handover/HrmUserHandoverApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.hrm.api.handover;
import java.util.Set;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥ API æŽ¥å£
 * <p>
 * ä¾› CRM、ERP、MES ç­‰æ¨¡å—调用,用于在查询"我的数据"时扩展用户ID范围,
 * ä½¿äº¤æŽ¥äººèƒ½æŸ¥çœ‹ç¦»èŒäººçš„业务数据
 *
 * @author èŠ‹é“æºç 
 */
public interface HrmUserHandoverApi {
    /**
     * æ‰©å±•用户ID集合,将交接源用户ID加入
     *
     * @param userIds ç”¨æˆ·ID集合
     * @return æ‰©å±•后的用户ID集合
     */
    Set<Long> expandUserIds(Set<Long> userIds);
}
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -45,6 +45,12 @@
    ErrorCode RESIGNATION_APPLICATION_EXISTS = new ErrorCode(1_030_003_005, "该员工已存在离职申请,请勿重复提交");
    ErrorCode RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_003_006, "离职审批流程不存在");
    // ========== ç”¨æˆ·æ•°æ®äº¤æŽ¥ 1-030-007-000 ==========
    ErrorCode USER_HANDOVER_NOT_EXISTS = new ErrorCode(1_030_007_000, "用户交接记录不存在");
    ErrorCode USER_HANDOVER_ALREADY_EXISTS = new ErrorCode(1_030_007_001, "该离职申请已存在交接记录");
    ErrorCode USER_HANDOVER_FROM_USER_NOT_EXISTS = new ErrorCode(1_030_007_002, "交接源用户不存在");
    ErrorCode USER_HANDOVER_TO_USER_NOT_EXISTS = new ErrorCode(1_030_007_003, "交接目标用户不存在");
    // ========== è€ƒå‹¤ç®¡ç† 1-030-004-000 ==========
    ErrorCode ATTENDANCE_RECORD_NOT_EXISTS = new ErrorCode(1_030_004_000, "考勤记录不存在");
    ErrorCode ATTENDANCE_RECORD_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_004_001, "导入考勤数据不能为空");
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmHandoverStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.hrm.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * HRM äº¤æŽ¥çŠ¶æ€æžšä¸¾
 *
 * @author èŠ‹é“æºç 
 */
@RequiredArgsConstructor
@Getter
public enum HrmHandoverStatusEnum implements ArrayValuable<Integer> {
    NOT_STARTED(0, "未开始"),
    IN_PROGRESS(10, "进行中"),
    COMPLETED(20, "已完成");
    private final Integer status;
    private final String name;
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(HrmHandoverStatusEnum::getStatus).toArray(Integer[]::new);
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
    public static String getNameByStatus(Integer status) {
        return Arrays.stream(values())
                .filter(e -> e.getStatus().equals(status))
                .findFirst()
                .map(HrmHandoverStatusEnum::getName)
                .orElse("");
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/api/handover/HrmUserHandoverApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.hrm.api.handover;
import cn.iocoder.yudao.module.hrm.service.handover.HrmUserHandoverService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.Set;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥ API å®žçް
 *
 * @author èŠ‹é“æºç 
 */
@Service
public class HrmUserHandoverApiImpl implements HrmUserHandoverApi {
    @Resource
    private HrmUserHandoverService handoverService;
    @Override
    public Set<Long> expandUserIds(Set<Long> userIds) {
        return handoverService.expandUserIds(userIds);
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmResignationApplicationController.java
@@ -9,6 +9,7 @@
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationHandoverReqVO;
import cn.iocoder.yudao.module.hrm.service.approval.HrmResignationApplicationService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -98,6 +99,14 @@
        return success(resignationApplicationService.getApproveProcessDefinitionList());
    }
    @PutMapping("/handover")
    @Operation(summary = "更新交接状态")
    @PreAuthorize("@ss.hasPermission('hrm:resignation:update')")
    public CommonResult<Boolean> updateHandoverStatus(@Valid @RequestBody HrmResignationHandoverReqVO reqVO) {
        resignationApplicationService.updateHandoverStatus(reqVO);
        return success(true);
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出离职申请 Excel")
    @PreAuthorize("@ss.hasPermission('hrm:resignation:export')")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationHandoverReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - ç¦»èŒäº¤æŽ¥çŠ¶æ€æ›´æ–° Request VO")
@Data
public class HrmResignationHandoverReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "交接状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    @NotNull(message = "交接状态不能为空")
    private Integer handoverStatus;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/handover/HrmUserHandoverDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.hrm.dal.dataobject.handover;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥æ˜ å°„ DO
 *
 * @author èŠ‹é“æºç 
 */
@TableName("hrm_user_handover")
@KeySequence("hrm_user_handover_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HrmUserHandoverDO extends BaseDO {
    /**
     * æ˜ å°„ID
     */
    @TableId
    private Long id;
    /**
     * ç¦»èŒç”¨æˆ·ID
     *
     * å…³è” AdminUserDO#getId()
     */
    private Long fromUserId;
    /**
     * äº¤æŽ¥ç›®æ ‡ç”¨æˆ·ID
     *
     * å…³è” AdminUserDO#getId()
     */
    private Long toUserId;
    /**
     * å…³è”的离职申请ID
     *
     * å…³è” HrmResignationApplicationDO#getId()
     */
    private Long resignationId;
    /**
     * äº¤æŽ¥çŠ¶æ€
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.hrm.enums.HrmHandoverStatusEnum}
     */
    private Integer handoverStatus;
    /**
     * äº¤æŽ¥å®Œæˆæ—¶é—´
     */
    private LocalDateTime handoverTime;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/handover/HrmUserHandoverMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.hrm.dal.mysql.handover;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.dal.dataobject.handover.HrmUserHandoverDO;
import cn.iocoder.yudao.module.hrm.enums.HrmHandoverStatusEnum;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Set;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥æ˜ å°„ Mapper
 *
 * @author èŠ‹é“æºç 
 */
@Mapper
public interface HrmUserHandoverMapper extends BaseMapperX<HrmUserHandoverDO> {
    default List<HrmUserHandoverDO> selectListByToUserIds(Set<Long> toUserIds) {
        return selectList(new LambdaQueryWrapperX<HrmUserHandoverDO>()
                .in(HrmUserHandoverDO::getToUserId, toUserIds)
                .eq(HrmUserHandoverDO::getHandoverStatus, HrmHandoverStatusEnum.COMPLETED.getStatus()));
    }
    default HrmUserHandoverDO selectByResignationId(Long resignationId) {
        return selectOne(HrmUserHandoverDO::getResignationId, resignationId);
    }
    default List<HrmUserHandoverDO> selectListByFromUserId(Long fromUserId) {
        return selectList(HrmUserHandoverDO::getFromUserId, fromUserId);
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationService.java
@@ -4,6 +4,7 @@
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationHandoverReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
import jakarta.validation.Valid;
@@ -105,4 +106,11 @@
     */
    HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId);
    /**
     * æ›´æ–°ç¦»èŒç”³è¯·äº¤æŽ¥çŠ¶æ€
     *
     * @param reqVO äº¤æŽ¥çŠ¶æ€æ›´æ–°ä¿¡æ¯
     */
    void updateHandoverStatus(@Valid HrmResignationHandoverReqVO reqVO);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -12,11 +12,13 @@
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationHandoverReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
import cn.iocoder.yudao.module.hrm.service.handover.HrmUserHandoverService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
@@ -73,6 +75,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private HrmUserHandoverService handoverService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -323,4 +327,22 @@
        return resignationApplicationMapper.selectByProcessInstanceId(processInstanceId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateHandoverStatus(HrmResignationHandoverReqVO reqVO) {
        HrmResignationApplicationDO application = validateResignationApplicationExists(reqVO.getId());
        resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
                .id(reqVO.getId())
                .handoverStatus(reqVO.getHandoverStatus())
                .build());
        // äº¤æŽ¥å®Œæˆæ—¶ï¼Œåˆ›å»ºç”¨æˆ·æ•°æ®äº¤æŽ¥æ˜ å°„
        if (Objects.equals(reqVO.getHandoverStatus(), HANDOVER_STATUS_COMPLETED)
                && application.getHandoverUserId() != null) {
            handoverService.createHandover(
                    application.getUserId(),
                    application.getHandoverUserId(),
                    application.getId());
        }
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/handover/HrmUserHandoverService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.hrm.service.handover;
import java.util.Set;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥ Service æŽ¥å£
 *
 * @author èŠ‹é“æºç 
 */
public interface HrmUserHandoverService {
    /**
     * åˆ›å»ºäº¤æŽ¥æ˜ å°„
     *
     * @param fromUserId    ç¦»èŒç”¨æˆ·ID
     * @param toUserId      äº¤æŽ¥ç›®æ ‡ç”¨æˆ·ID
     * @param resignationId ç¦»èŒç”³è¯·ID
     */
    void createHandover(Long fromUserId, Long toUserId, Long resignationId);
    /**
     * æ‰©å±•用户ID集合,将交接源用户ID加入
     * <p>
     * æŸ¥è¯¢æ‰€æœ‰å·²å®Œæˆçš„交接记录,如果 userIds ä¸­æœ‰äº¤æŽ¥ç›®æ ‡ç”¨æˆ·(toUserId),
     * åˆ™æŠŠå¯¹åº”的离职用户(fromUserId)也加入结果集
     *
     * @param userIds ç”¨æˆ·ID集合
     * @return æ‰©å±•后的用户ID集合(包含交接源用户)
     */
    Set<Long> expandUserIds(Set<Long> userIds);
    /**
     * åˆ é™¤äº¤æŽ¥æ˜ å°„
     *
     * @param id æ˜ å°„ID
     */
    void deleteHandover(Long id);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/handover/HrmUserHandoverServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package cn.iocoder.yudao.module.hrm.service.handover;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.hrm.dal.dataobject.handover.HrmUserHandoverDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.handover.HrmUserHandoverMapper;
import cn.iocoder.yudao.module.hrm.enums.HrmHandoverStatusEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
/**
 * ç”¨æˆ·æ•°æ®äº¤æŽ¥ Service å®žçް
 *
 * @author èŠ‹é“æºç 
 */
@Service
@Validated
@Slf4j
public class HrmUserHandoverServiceImpl implements HrmUserHandoverService {
    @Resource
    private HrmUserHandoverMapper userHandoverMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createHandover(Long fromUserId, Long toUserId, Long resignationId) {
        // æ ¡éªŒæºç”¨æˆ·å­˜åœ¨
        AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId);
        if (fromUser == null) {
            throw exception(USER_HANDOVER_FROM_USER_NOT_EXISTS);
        }
        // æ ¡éªŒç›®æ ‡ç”¨æˆ·å­˜åœ¨
        AdminUserRespDTO toUser = adminUserApi.getUser(toUserId);
        if (toUser == null) {
            throw exception(USER_HANDOVER_TO_USER_NOT_EXISTS);
        }
        // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨
        HrmUserHandoverDO existing = userHandoverMapper.selectByResignationId(resignationId);
        if (existing != null) {
            throw exception(USER_HANDOVER_ALREADY_EXISTS);
        }
        HrmUserHandoverDO record = HrmUserHandoverDO.builder()
                .fromUserId(fromUserId)
                .toUserId(toUserId)
                .resignationId(resignationId)
                .handoverStatus(HrmHandoverStatusEnum.COMPLETED.getStatus())
                .handoverTime(LocalDateTime.now())
                .build();
        userHandoverMapper.insert(record);
        log.info("[createHandover] åˆ›å»ºäº¤æŽ¥æ˜ å°„: fromUserId={}, toUserId={}, resignationId={}", fromUserId, toUserId, resignationId);
    }
    @Override
    public Set<Long> expandUserIds(Set<Long> userIds) {
        if (CollUtil.isEmpty(userIds)) {
            return Collections.emptySet();
        }
        List<HrmUserHandoverDO> handoverList = userHandoverMapper.selectListByToUserIds(userIds);
        if (CollUtil.isEmpty(handoverList)) {
            return new HashSet<>(userIds);
        }
        Set<Long> fromUserIds = handoverList.stream()
                .map(HrmUserHandoverDO::getFromUserId)
                .collect(Collectors.toSet());
        Set<Long> result = new HashSet<>(userIds);
        result.addAll(fromUserIds);
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteHandover(Long id) {
        HrmUserHandoverDO record = userHandoverMapper.selectById(id);
        if (record == null) {
            throw exception(USER_HANDOVER_NOT_EXISTS);
        }
        userHandoverMapper.deleteById(id);
        log.info("[deleteHandover] åˆ é™¤äº¤æŽ¥æ˜ å°„: id={}, fromUserId={}, toUserId={}", id, record.getFromUserId(), record.getToUserId());
    }
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -185,6 +185,13 @@
        return success(BeanUtils.toBean(list, MdmItemRespVO.class));
    }
    @GetMapping("/simple-list")
    @Operation(summary = "获取物料精简列表(启用状态,用于下拉选择)")
    public CommonResult<List<MdmItemRespVO>> getItemSimpleList() {
        List<MdmItemDO> list = itemService.getItemSimpleList();
        return success(BeanUtils.toBean(list, MdmItemRespVO.class));
    }
    /**
     * å¡«å……物料响应 VO çš„关联名称(分类、主单位、辅单位、仓库名称)
     */
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
@@ -138,4 +138,11 @@
     */
    MdmItemDO getItemByCode(String code);
    /**
     * èŽ·å¾—ç‰©æ–™ç²¾ç®€åˆ—è¡¨ï¼ˆå¯ç”¨çŠ¶æ€ï¼Œç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
     *
     * @return ç‰©æ–™åˆ—表
     */
    List<MdmItemDO> getItemSimpleList();
}
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -332,6 +332,12 @@
        return itemMapper.selectByCode(code);
    }
    @Override
    public List<MdmItemDO> getItemSimpleList() {
        return itemMapper.selectList(MdmItemDO::getStatus,
                cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus());
    }
    /**
     * ä¿å­˜æ‰¹æ¬¡é…ç½®ï¼ˆåˆ° MDM æœ¬åœ°è¡¨ï¼‰
     */
yudao-module-mes/pom.xml
@@ -59,6 +59,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- SRM ä¾›åº”商关系管理 API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-srm-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <!-- Web ç›¸å…³ -->
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
@@ -15,8 +15,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
@@ -53,7 +53,7 @@
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
@@ -167,9 +167,9 @@
                convertSet(list, MesProWorkOrderDO::getProductId));
        Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(
                convertSet(list, MesProWorkOrderDO::getClientId));
        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesProWorkOrderDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        // å•位从产品关联的 item èŽ·å¾—
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/MesQcIqcController.java
@@ -10,8 +10,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
@@ -50,7 +50,7 @@
    @Resource
    private MesQcIqcService iqcService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesMdItemService itemService;
    @Resource
@@ -128,9 +128,9 @@
            return Collections.emptyList();
        }
        // æ‰¹é‡æŸ¥è¯¢ä¾›åº”商
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesQcIqcDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        // æ‰¹é‡æŸ¥è¯¢ç‰©æ–™
        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
                convertSet(list, MesQcIqcDO::getItemId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/pendinginspect/MesQcPendingInspectController.java
@@ -7,8 +7,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
@@ -48,7 +48,7 @@
    @Resource
    private MesMdUnitMeasureService unitMeasureService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
@@ -80,9 +80,9 @@
        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
        // æ‰¹é‡æŸ¥è¯¢ä¾›åº”商(IQC åœºæ™¯ï¼‰
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesQcPendingInspectRespVO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        // æ‰¹é‡æŸ¥è¯¢å®¢æˆ·ï¼ˆOQC/RQC åœºæ™¯ï¼‰
        Map<Long, CrmCustomerRespDTO> clientMap = customerApi.getCustomerMap(
                convertSet(list, MesQcPendingInspectRespVO::getClientId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -14,9 +14,10 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -45,7 +46,7 @@
    @Resource
    private MesWmArrivalNoticeService arrivalNoticeService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
@@ -130,9 +131,9 @@
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—ä¾›åº”å•†ä¿¡æ¯ï¼ˆERP é‡‡è´­ä¾›åº”商)
        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmArrivalNoticeDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 2. èŽ·å¾—é‡‡è´­è®¢å•ä¿¡æ¯
        List<Long> purchaseOrderIds = list.stream()
                .map(MesWmArrivalNoticeDO::getPurchaseOrderId)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java
@@ -9,8 +9,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchRespVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -60,7 +60,7 @@
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
@@ -116,9 +116,9 @@
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmBatchDO::getItemId));
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmBatchDO::getVendorId)).getCheckedData().stream()
                .collect(Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        Map<Long, CrmCustomerRespDTO> clientMap = customerApi.getCustomerMap(
                convertSet(list, MesWmBatchDO::getClientId));
        Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
@@ -17,8 +17,8 @@
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -47,7 +47,7 @@
    @Resource
    private MesWmItemReceiptService itemReceiptService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesQcIqcService iqcService;
    @Resource
@@ -172,9 +172,9 @@
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®ï¼ˆé‡‡è´­å…¥åº“ä½¿ç”¨ ERP ä¾›åº”商)
        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> supplierMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmItemReceiptDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, v -> v, (a, b) -> a));
        Map<Long, MesQcIqcDO> iqcMap = iqcService.getIqcMap(
                convertSet(list, MesWmItemReceiptDO::getIqcId));
        Map<Long, MesWmArrivalNoticeDO> noticeMap = arrivalNoticeService.getArrivalNoticeMap(
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -13,8 +13,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockRespVO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -69,7 +69,7 @@
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @GetMapping("/get")
    @Operation(summary = "获得库存记录")
@@ -153,9 +153,9 @@
                convertSet(list, MesWmMaterialStockDO::getLocationId));
        Map<Long, MesWmWarehouseAreaDO> areaMap = areaService.getWarehouseAreaMap(
                convertSet(list, MesWmMaterialStockDO::getAreaId));
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmMaterialStockDO::getVendorId)).getCheckedData().stream()
                .collect(Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmMaterialStockRespVO.class, vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java
@@ -11,8 +11,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssuePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.MesWmOutsourceIssueSaveReqVO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourceissue.MesWmOutsourceIssueDO;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
@@ -45,7 +45,7 @@
    @Resource
    private MesWmOutsourceIssueService outsourceIssueService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesProWorkOrderService workOrderService;
@@ -157,9 +157,9 @@
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmOutsourceIssueDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
                convertSet(list, MesWmOutsourceIssueDO::getWorkOrderId));
        // 2. æž„建结果
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourcereceipt/MesWmOutsourceReceiptController.java
@@ -11,8 +11,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo.MesWmOutsourceReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo.MesWmOutsourceReceiptRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourcereceipt.vo.MesWmOutsourceReceiptSaveReqVO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutsourceReceiptDO;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
@@ -45,7 +45,7 @@
    @Resource
    private MesWmOutsourceReceiptService outsourceReceiptService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesProWorkOrderService workOrderService;
@@ -149,9 +149,9 @@
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmOutsourceReceiptDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
                convertSet(list, MesWmOutsourceReceiptDO::getWorkOrderId));
        // 2. æž„建结果
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
@@ -11,8 +11,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorSaveReqVO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDO;
import cn.iocoder.yudao.module.mes.service.wm.returnvendor.MesWmReturnVendorService;
import io.swagger.v3.oas.annotations.Operation;
@@ -43,7 +43,7 @@
    @Resource
    private MesWmReturnVendorService returnVendorService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @PostMapping("/create")
    @Operation(summary = "创建供应商退货单")
@@ -153,9 +153,9 @@
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, ErpSupplierRespDTO> vendorMap = supplierApi.getSupplierList(
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmReturnVendorDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, s -> s));
                .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmReturnVendorRespVO.class, vo ->
                MapUtils.findAndThen(vendorMap, vo.getVendorId(), vendor ->
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -22,7 +22,7 @@
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
@@ -63,7 +63,7 @@
    @Resource
    private MesWmOutsourceReceiptService outsourceReceiptService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesMdItemService itemService;
    @Resource
@@ -123,7 +123,7 @@
        validateIqcCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在
        if (reqVO.getVendorId() != null) {
            supplierApi.validateSupplier(reqVO.getVendorId());
            srmSupplierApi.getSupplier(reqVO.getVendorId());
        }
        // æ ¡éªŒç‰©æ–™å­˜åœ¨ï¼ˆä½¿ç”¨ MDM ç‰©æ–™ï¼‰
        if (reqVO.getItemId() != null) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -12,7 +12,7 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
@@ -48,7 +48,7 @@
    private MesWmArrivalNoticeLineService arrivalNoticeLineService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
@@ -88,7 +88,7 @@
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在(ERP é‡‡è´­ä¾›åº”商)
        supplierApi.validateSupplier(reqVO.getVendorId());
        srmSupplierApi.getSupplier(reqVO.getVendorId());
        // æ ¡éªŒé‡‡è´­è®¢å•存在
        if (reqVO.getPurchaseOrderId() != null) {
            ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -26,7 +26,7 @@
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import org.springframework.security.core.Authentication;
@@ -67,7 +67,7 @@
    private MesWmArrivalNoticeService arrivalNoticeService;
    @Resource
    @Lazy
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    @Lazy
    private MesQcIqcService iqcService;
@@ -114,7 +114,7 @@
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在(采购入库使用 ERP ä¾›åº”商)
        supplierApi.validateSupplier(reqVO.getVendorId());
        srmSupplierApi.getSupplier(reqVO.getVendorId());
        // æ ¡éªŒåˆ°è´§é€šçŸ¥å•存在
        if (reqVO.getNoticeId() != null) {
            MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java
@@ -21,7 +21,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceIssueStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -56,7 +56,7 @@
    @Resource
    private MesWmOutsourceIssueDetailService outsourceIssueDetailService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
@@ -259,7 +259,7 @@
        validateCodeUnique(saveReqVO.getId(), saveReqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在
        if (saveReqVO.getVendorId() != null) {
            supplierApi.validateSupplier(saveReqVO.getVendorId());
            srmSupplierApi.getSupplier(saveReqVO.getVendorId());
        }
        // æ ¡éªŒå·¥å•存在且类型为外协(代工)
        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(saveReqVO.getWorkOrderId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java
@@ -21,7 +21,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -56,7 +56,7 @@
    @Resource
    private MesWmTransactionService wmTransactionService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
@@ -309,7 +309,7 @@
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(id, reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在
        supplierApi.validateSupplier(reqVO.getVendorId());
        srmSupplierApi.getSupplier(reqVO.getVendorId());
        // æ ¡éªŒå¤–协工单存在
        if (reqVO.getWorkOrderId() != null) {
            workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -15,7 +15,7 @@
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnVendorStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import jakarta.annotation.Resource;
@@ -45,7 +45,7 @@
    @Resource
    private MesWmReturnVendorDetailService returnVendorDetailService;
    @Resource
    private ErpSupplierApi supplierApi;
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesWmTransactionService wmTransactionService;
@@ -217,7 +217,7 @@
            validateReturnVendorExistsAndPrepare(reqVO.getId());
        }
        // æ ¡éªŒä¾›åº”商存在
        supplierApi.validateSupplier(reqVO.getVendorId());
        srmSupplierApi.getSupplier(reqVO.getVendorId());
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(reqVO.getId(), reqVO.getCode());
    }
yudao-module-srm-api/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-srm-api</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        srm æ¨¡å—下,供应商关系管理的 API æŽ¥å£å®šä¹‰ï¼Œä¾›å…¶ä»–模块引用
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-common</artifactId>
        </dependency>
        <!-- Swagger æ³¨è§£ -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
yudao-module-srm-api/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/SrmSupplierApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.srm.api.supplier;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import java.util.Collection;
import java.util.List;
/**
 * SRM ä¾›åº”商 API æŽ¥å£
 */
public interface SrmSupplierApi {
    String PREFIX = "/rpc-api/srm/supplier";
    /**
     * èŽ·å–ä¾›åº”å•†ï¼ˆé€šè¿‡ SRM ID)
     *
     * @param srmId SRM ä¾›åº”商 ID
     * @return ä¾›åº”商
     */
    CommonResult<SrmSupplierRespDTO> getSupplier(Long srmId);
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨
     *
     * @param ids SRM ä¾›åº”商 ID åˆ—表
     * @return ä¾›åº”商列表
     */
    CommonResult<List<SrmSupplierRespDTO>> getSupplierList(Collection<Long> ids);
    /**
     * èŽ·å–ä¾›åº”å•†ç²¾ç®€åˆ—è¡¨ï¼ˆä»…è¿”å›žå·²é€šè¿‡å‡†å…¥å®¡æ‰¹çš„ä¾›åº”å•†ï¼Œç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
     *
     * @return ä¾›åº”商列表
     */
    CommonResult<List<SrmSupplierRespDTO>> getSupplierSimpleList();
}
yudao-module-srm-api/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/dto/SrmSupplierRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.srm.api.supplier.dto;
import lombok.Data;
/**
 * SRM ä¾›åº”商 Response DTO
 */
@Data
public class SrmSupplierRespDTO {
    /**
     * SRM ä¾›åº”商 ID
     */
    private Long id;
    /**
     * ä¾›åº”商编码
     */
    private String code;
    /**
     * ä¾›åº”商名称
     */
    private String name;
    /**
     * å¯ç”¨çŠ¶æ€
     */
    private Integer status;
}
yudao-module-srm/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-srm</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>
        srm åŒ…下,供应商关系管理(Supplier Relationship Management)。
        ä¾‹å¦‚说:供应商档案、供应商准入、资质管理、绩效评价、招投标等等
    </description>
    <dependencies>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- BPM å·¥ä½œæµ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-bpm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ERP API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-erp-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- MDM API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mdm-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- MES API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mes-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- SRM API(本模块 API) -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-srm-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- Web ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-security</artifactId>
        </dependency>
        <!-- DB ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>
        <!-- å·¥å…·ç±»ç›¸å…³ -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-excel</artifactId>
        </dependency>
        <!-- Test æµ‹è¯•相关 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/api/supplier/SrmSupplierApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.srm.api.supplier;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * SRM ä¾›åº”商 API å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmSupplierApiImpl implements SrmSupplierApi {
    @Resource
    private SrmSupplierService supplierService;
    @Override
    public CommonResult<SrmSupplierRespDTO> getSupplier(Long srmId) {
        SrmSupplierDO supplier = supplierService.getSupplier(srmId);
        if (supplier == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
        }
        return success(toRespDTO(supplier));
    }
    @Override
    public CommonResult<List<SrmSupplierRespDTO>> getSupplierList(Collection<Long> ids) {
        List<SrmSupplierDO> list = supplierService.getSupplierList(ids);
        return success(list.stream().map(this::toRespDTO).collect(Collectors.toList()));
    }
    @Override
    public CommonResult<List<SrmSupplierRespDTO>> getSupplierSimpleList() {
        List<SrmSupplierDO> list = supplierService.getSupplierSimpleList();
        return success(list.stream().map(this::toRespDTO).collect(Collectors.toList()));
    }
    private SrmSupplierRespDTO toRespDTO(SrmSupplierDO supplier) {
        SrmSupplierRespDTO dto = new SrmSupplierRespDTO();
        dto.setId(supplier.getId());
        dto.setCode(supplier.getCode());
        dto.setName(supplier.getName());
        dto.setStatus(supplier.getStatus());
        return dto;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/SrmSupplierApplyController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.srm.controller.admin.apply;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierApplyDO;
import cn.iocoder.yudao.module.srm.service.apply.SrmSupplierApplyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM ä¾›åº”商准入")
@RestController
@RequestMapping("/srm/supplier-apply")
@Validated
public class SrmSupplierApplyController {
    @Resource
    private SrmSupplierApplyService applyService;
    @PostMapping("/create")
    @Operation(summary = "创建准入申请")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:create')")
    public CommonResult<Long> createApply(@Valid @RequestBody SrmSupplierApplySaveReqVO createReqVO) {
        return success(applyService.createApply(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新准入申请")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> updateApply(@Valid @RequestBody SrmSupplierApplySaveReqVO updateReqVO) {
        applyService.updateApply(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除准入申请")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:delete')")
    public CommonResult<Boolean> deleteApply(@RequestParam("id") Long id) {
        applyService.deleteApply(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取准入申请详情")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:query')")
    public CommonResult<SrmSupplierApplyRespVO> getApply(@RequestParam("id") Long id) {
        return success(BeanUtils.toBean(applyService.getApply(id), SrmSupplierApplyRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获取准入申请分页")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:query')")
    public CommonResult<PageResult<SrmSupplierApplyRespVO>> getApplyPage(@Valid SrmSupplierApplyPageReqVO pageReqVO) {
        return success(BeanUtils.toBean(applyService.getApplyPage(pageReqVO), SrmSupplierApplyRespVO.class));
    }
    @PutMapping("/submit")
    @Operation(summary = "提交申请")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> submitApply(@RequestParam("id") Long id) {
        applyService.submitApply(id);
        return success(true);
    }
    @PutMapping("/review-purchase")
    @Operation(summary = "采购审核")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> reviewPurchase(@Valid @RequestBody SrmSupplierApplyReviewReqVO reqVO) {
        applyService.reviewPurchase(reqVO.getId(), reqVO.getOpinion(), reqVO.getPassed());
        return success(true);
    }
    @PutMapping("/review-quality")
    @Operation(summary = "质量审核")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> reviewQuality(@Valid @RequestBody SrmSupplierApplyReviewReqVO reqVO) {
        applyService.reviewQuality(reqVO.getId(), reqVO.getOpinion(), reqVO.getPassed());
        return success(true);
    }
    @PutMapping("/review-finance")
    @Operation(summary = "财务审核")
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> reviewFinance(@Valid @RequestBody SrmSupplierApplyReviewReqVO reqVO) {
        applyService.reviewFinance(reqVO.getId(), reqVO.getOpinion(), reqVO.getPassed());
        return success(true);
    }
    @PutMapping("/approve")
    @Operation(summary = "通过后自动创建供应商")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-apply:update')")
    public CommonResult<Boolean> approve(@RequestParam("id") Long id) {
        applyService.approve(id);
        return success(true);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.controller.admin.apply.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - SRM ä¾›åº”商准入申请分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmSupplierApplyPageReqVO extends PageParam {
    @Schema(description = "申请编号", example = "AP2024010001")
    private String applyNo;
    @Schema(description = "供应商名称", example = "XX科技有限公司")
    private String supplierName;
    @Schema(description = "申请状态", example = "0")
    private Integer applyStatus;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.srm.controller.admin.apply.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM ä¾›åº”商准入申请 Response VO")
@Data
public class SrmSupplierApplyRespVO {
    @Schema(description = "申请ID", example = "1")
    private Long id;
    @Schema(description = "申请编号", example = "AP2024010001")
    private String applyNo;
    @Schema(description = "供应商ID(SRM供应商ID,审批通过后回填)", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "XX科技有限公司")
    private String supplierName;
    @Schema(description = "供应商类型", example = "1")
    private String supplierType;
    @Schema(description = "企业性质", example = "有限责任公司")
    private String companyNature;
    @Schema(description = "注册资金(万元)", example = "1000")
    private BigDecimal registeredCapital;
    @Schema(description = "营业执照编号", example = "91310000XXXXXXXXXX")
    private String businessLicenseNo;
    @Schema(description = "税号", example = "91310000XXXXXXXXXX")
    private String taxNo;
    @Schema(description = "公司地址", example = "上海市浦东新区XXè·¯XX号")
    private String address;
    @Schema(description = "联系人", example = "张三")
    private String contactName;
    @Schema(description = "联系电话", example = "13800138000")
    private String contactMobile;
    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String contactEmail;
    @Schema(description = "申请人ID", example = "1")
    private Long applyUserId;
    @Schema(description = "申请人名称", example = "张三")
    private String applyUserName;
    @Schema(description = "申请时间")
    private LocalDateTime applyTime;
    @Schema(description = "申请状态", example = "0")
    private Integer applyStatus;
    @Schema(description = "采购审核人ID", example = "2")
    private Long purchaseReviewerId;
    @Schema(description = "采购审核时间")
    private LocalDateTime purchaseReviewTime;
    @Schema(description = "采购审核意见", example = "同意")
    private String purchaseReviewOpinion;
    @Schema(description = "质量审核人ID", example = "3")
    private Long qualityReviewerId;
    @Schema(description = "质量审核时间")
    private LocalDateTime qualityReviewTime;
    @Schema(description = "质量审核意见", example = "同意")
    private String qualityReviewOpinion;
    @Schema(description = "财务审核人ID", example = "4")
    private Long financeReviewerId;
    @Schema(description = "财务审核时间")
    private LocalDateTime financeReviewTime;
    @Schema(description = "财务审核意见", example = "同意")
    private String financeReviewOpinion;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyReviewReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.srm.controller.admin.apply.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - SRM ä¾›åº”商准入申请审核 Request VO")
@Data
public class SrmSupplierApplyReviewReqVO {
    @Schema(description = "申请ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "申请ID不能为空")
    private Long id;
    @Schema(description = "审核意见", example = "通过")
    private String opinion;
    @Schema(description = "是否通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "审核结果不能为空")
    private Boolean passed;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.srm.controller.admin.apply.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM ä¾›åº”商准入申请创建/更新 Request VO")
@Data
public class SrmSupplierApplySaveReqVO {
    @Schema(description = "申请ID", example = "1")
    private Long id;
    @Schema(description = "供应商ID(SRM供应商ID,审批通过后回填)", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "XX科技有限公司")
    private String supplierName;
    @Schema(description = "供应商类型", example = "1")
    private String supplierType;
    @Schema(description = "企业性质", example = "有限责任公司")
    private String companyNature;
    @Schema(description = "注册资金(万元)", example = "1000")
    private BigDecimal registeredCapital;
    @Schema(description = "营业执照编号", example = "91310000XXXXXXXXXX")
    private String businessLicenseNo;
    @Schema(description = "税号", example = "91310000XXXXXXXXXX")
    private String taxNo;
    @Schema(description = "公司地址", example = "上海市浦东新区XXè·¯XX号")
    private String address;
    @Schema(description = "联系人", example = "张三")
    private String contactName;
    @Schema(description = "联系电话", example = "13800138000")
    private String contactMobile;
    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String contactEmail;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
package cn.iocoder.yudao.module.srm.controller.admin.certificate;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.service.certificate.SrmSupplierCertificateService;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - SRM ä¾›åº”商资质")
@RestController
@RequestMapping("/srm/supplier-certificate")
@Validated
public class SrmSupplierCertificateController {
    @Resource
    private SrmSupplierCertificateService certificateService;
    @Resource
    private SrmSupplierService supplierService;
    @PostMapping("/create")
    @Operation(summary = "创建资质证书")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:create')")
    public CommonResult<Long> createCertificate(@Valid @RequestBody SrmSupplierCertificateSaveReqVO createReqVO) {
        return success(certificateService.createCertificate(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新资质证书")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:update')")
    public CommonResult<Boolean> updateCertificate(@Valid @RequestBody SrmSupplierCertificateSaveReqVO updateReqVO) {
        certificateService.updateCertificate(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除资质证书")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:delete')")
    public CommonResult<Boolean> deleteCertificate(@RequestParam("id") Long id) {
        certificateService.deleteCertificate(id);
        return success(true);
    }
    @GetMapping("/page")
    @Operation(summary = "获取资质证书分页")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:query')")
    public CommonResult<PageResult<SrmSupplierCertificateRespVO>> getCertificatePage(@Valid SrmSupplierCertificatePageReqVO pageReqVO) {
        var pageResult = certificateService.getCertificatePage(pageReqVO);
        var respList = BeanUtils.toBean(pageResult.getList(), SrmSupplierCertificateRespVO.class);
        enrichSupplierNames(respList);
        return success(new PageResult<>(respList, pageResult.getTotal()));
    }
    @GetMapping("/list-by-supplier")
    @Operation(summary = "根据供应商ID获取资质列表")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:query')")
    public CommonResult<List<SrmSupplierCertificateRespVO>> getListBySupplier(@RequestParam("supplierId") Long supplierId) {
        var list = BeanUtils.toBean(certificateService.getCertificateListBySupplierId(supplierId), SrmSupplierCertificateRespVO.class);
        enrichSupplierNames(list);
        return success(list);
    }
    @GetMapping("/expiring-list")
    @Operation(summary = "获取即将到期的资质列表(30天内)")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:query')")
    public CommonResult<List<SrmSupplierCertificateRespVO>> getExpiringList() {
        var list = BeanUtils.toBean(certificateService.getExpiringCertificates(), SrmSupplierCertificateRespVO.class);
        enrichSupplierNames(list);
        return success(list);
    }
    @GetMapping("/get")
    @Operation(summary = "获取资质证书详情")
    @PreAuthorize("@ss.hasPermission('srm:supplier-certificate:query')")
    public CommonResult<SrmSupplierCertificateRespVO> getCertificate(@RequestParam("id") Long id) {
        var resp = BeanUtils.toBean(certificateService.getCertificate(id), SrmSupplierCertificateRespVO.class);
        enrichSupplierName(resp);
        return success(resp);
    }
    // ========== å…³è”数据填充 ==========
    private void enrichSupplierNames(List<SrmSupplierCertificateRespVO> list) {
        if (list == null || list.isEmpty()) return;
        Set<Long> supplierIds = convertSet(list, SrmSupplierCertificateRespVO::getSupplierId);
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
        list.forEach(vo -> {
            SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId());
            if (supplier != null) {
                vo.setSupplierName(supplier.getName());
            }
        });
    }
    private void enrichSupplierName(SrmSupplierCertificateRespVO vo) {
        if (vo == null || vo.getSupplierId() == null) return;
        SrmSupplierDO supplier = supplierService.getSupplier(vo.getSupplierId());
        if (supplier != null) {
            vo.setSupplierName(supplier.getName());
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.srm.controller.admin.certificate.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - SRM ä¾›åº”商资质证书分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmSupplierCertificatePageReqVO extends PageParam {
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "证书类型", example = "1")
    private Integer certificateType;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.srm.controller.admin.certificate.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM ä¾›åº”商资质证书 Response VO")
@Data
public class SrmSupplierCertificateRespVO {
    @Schema(description = "证书ID", example = "1")
    private Long id;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "华为技术有限公司")
    private String supplierName;
    @Schema(description = "证书类型", example = "1")
    private Integer certificateType;
    @Schema(description = "证书名称", example = "ISO9001质量管理体系")
    private String certificateName;
    @Schema(description = "证书编号", example = "ISO9001-2020-XXXX")
    private String certificateNo;
    @Schema(description = "文件名", example = "iso9001_cert.pdf")
    private String fileName;
    @Schema(description = "文件地址", example = "https://example.com/files/xxx.pdf")
    private String fileUrl;
    @Schema(description = "发证机构", example = "中国质量认证中心")
    private String issuingAuthority;
    @Schema(description = "有效期开始", example = "2024-01-01")
    private LocalDate validStartDate;
    @Schema(description = "有效期结束", example = "2027-01-01")
    private LocalDate validEndDate;
    @Schema(description = "状态", example = "0")
    private Integer status;
    @Schema(description = "备注", example = "年度资质审核")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.srm.controller.admin.certificate.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDate;
@Schema(description = "管理后台 - SRM ä¾›åº”商资质证书创建/更新 Request VO")
@Data
public class SrmSupplierCertificateSaveReqVO {
    @Schema(description = "证书ID", example = "1")
    private Long id;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "证书类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "证书类型不能为空")
    private Integer certificateType;
    @Schema(description = "证书名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "ISO9001质量管理体系")
    @NotEmpty(message = "证书名称不能为空")
    private String certificateName;
    @Schema(description = "证书编号", example = "ISO9001-2020-XXXX")
    private String certificateNo;
    @Schema(description = "文件名", example = "iso9001_cert.pdf")
    private String fileName;
    @Schema(description = "文件地址", example = "https://example.com/files/xxx.pdf")
    private String fileUrl;
    @Schema(description = "发证机构", example = "中国质量认证中心")
    private String issuingAuthority;
    @Schema(description = "有效期开始", example = "2024-01-01")
    private LocalDate validStartDate;
    @Schema(description = "有效期结束", example = "2027-01-01")
    private LocalDate validEndDate;
    @Schema(description = "备注", example = "年度资质审核")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/SrmSupplierEvaluationController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.srm.controller.admin.evaluation;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierEvaluationDO;
import cn.iocoder.yudao.module.srm.service.evaluation.SrmSupplierEvaluationService;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - SRM ä¾›åº”商绩效评价")
@RestController
@RequestMapping("/srm/supplier-evaluation")
@Validated
public class SrmSupplierEvaluationController {
    @Resource
    private SrmSupplierEvaluationService evaluationService;
    @Resource
    private SrmSupplierService supplierService;
    @PostMapping("/create")
    @Operation(summary = "创建绩效评价")
    @PreAuthorize("@ss.hasPermission('srm:supplier-evaluation:create')")
    public CommonResult<Long> createEvaluation(@Valid @RequestBody SrmSupplierEvaluationSaveReqVO createReqVO) {
        return success(evaluationService.createEvaluation(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新绩效评价")
    @PreAuthorize("@ss.hasPermission('srm:supplier-evaluation:update')")
    public CommonResult<Boolean> updateEvaluation(@Valid @RequestBody SrmSupplierEvaluationSaveReqVO updateReqVO) {
        evaluationService.updateEvaluation(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除绩效评价")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-evaluation:delete')")
    public CommonResult<Boolean> deleteEvaluation(@RequestParam("id") Long id) {
        evaluationService.deleteEvaluation(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取绩效评价详情")
    @PreAuthorize("@ss.hasPermission('srm:supplier-evaluation:query')")
    public CommonResult<SrmSupplierEvaluationRespVO> getEvaluation(@RequestParam("id") Long id) {
        SrmSupplierEvaluationDO evaluation = evaluationService.getEvaluation(id);
        SrmSupplierEvaluationRespVO resp = BeanUtils.toBean(evaluation, SrmSupplierEvaluationRespVO.class);
        enrichSupplierName(resp);
        resp.setGrade(evaluation.getEvaluationLevel());
        return success(resp);
    }
    @GetMapping("/page")
    @Operation(summary = "获取绩效评价分页")
    @PreAuthorize("@ss.hasPermission('srm:supplier-evaluation:query')")
    public CommonResult<PageResult<SrmSupplierEvaluationRespVO>> getEvaluationPage(@Valid SrmSupplierEvaluationPageReqVO pageReqVO) {
        PageResult<SrmSupplierEvaluationDO> pageResult = evaluationService.getEvaluationPage(pageReqVO);
        List<SrmSupplierEvaluationRespVO> respList = BeanUtils.toBean(pageResult.getList(), SrmSupplierEvaluationRespVO.class);
        respList.forEach(vo -> vo.setGrade(vo.getEvaluationLevel()));
        enrichSupplierNames(respList);
        return success(new PageResult<>(respList, pageResult.getTotal()));
    }
    // ========== å…³è”数据填充 ==========
    private void enrichSupplierNames(List<SrmSupplierEvaluationRespVO> list) {
        if (list == null || list.isEmpty()) return;
        Set<Long> supplierIds = convertSet(list, SrmSupplierEvaluationRespVO::getSupplierId);
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
        list.forEach(vo -> {
            SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId());
            if (supplier != null) {
                vo.setSupplierName(supplier.getName());
            }
        });
    }
    private void enrichSupplierName(SrmSupplierEvaluationRespVO vo) {
        if (vo == null || vo.getSupplierId() == null) return;
        SrmSupplierDO supplier = supplierService.getSupplier(vo.getSupplierId());
        if (supplier != null) {
            vo.setSupplierName(supplier.getName());
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - SRM ä¾›åº”商绩效评价分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmSupplierEvaluationPageReqVO extends PageParam {
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "评价周期", example = "2024-Q1")
    private String evaluationPeriod;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM ä¾›åº”商绩效评价 Response VO")
@Data
public class SrmSupplierEvaluationRespVO {
    @Schema(description = "评价ID", example = "1")
    private Long id;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "XX科技有限公司")
    private String supplierName;
    @Schema(description = "评价周期", example = "2024-Q1")
    private String evaluationPeriod;
    @Schema(description = "质量评分(百分制)", example = "90")
    private BigDecimal qualityScore;
    @Schema(description = "质量合格率", example = "98.5")
    private BigDecimal qualityPassRate;
    @Schema(description = "质量缺陷次数", example = "2")
    private Integer qualityDefectCount;
    @Schema(description = "交付评分(百分制)", example = "85")
    private BigDecimal deliveryScore;
    @Schema(description = "交付准时率", example = "95.0")
    private BigDecimal deliveryOnTimeRate;
    @Schema(description = "交付延迟次数", example = "3")
    private Integer deliveryDelayCount;
    @Schema(description = "价格评分(百分制)", example = "80")
    private BigDecimal priceScore;
    @Schema(description = "服务评分(百分制)", example = "88")
    private BigDecimal serviceScore;
    @Schema(description = "服务响应速度(小时)", example = "2")
    private Integer serviceResponseSpeed;
    @Schema(description = "综合评分", example = "87.5")
    private BigDecimal compositeScore;
    @Schema(description = "评价等级(A/B/C/D)", example = "A")
    private String evaluationLevel;
    @Schema(description = "评价等级(同 evaluationLevel,适配前端)", example = "A")
    private String grade;
    @Schema(description = "评价人ID", example = "1")
    private Long evaluatorId;
    @Schema(description = "评价人名称", example = "张三")
    private String evaluatorName;
    @Schema(description = "评价时间")
    private LocalDateTime evaluationTime;
    @Schema(description = "备注", example = "本季度表现良好")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM ä¾›åº”商绩效评价创建/更新 Request VO")
@Data
public class SrmSupplierEvaluationSaveReqVO {
    @Schema(description = "评价ID", example = "1")
    private Long id;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "评价周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-Q1")
    @NotEmpty(message = "评价周期不能为空")
    private String evaluationPeriod;
    @Schema(description = "质量评分(百分制)", example = "90")
    private BigDecimal qualityScore;
    @Schema(description = "质量合格率", example = "98.5")
    private BigDecimal qualityPassRate;
    @Schema(description = "质量缺陷次数", example = "2")
    private Integer qualityDefectCount;
    @Schema(description = "交付评分(百分制)", example = "85")
    private BigDecimal deliveryScore;
    @Schema(description = "交付准时率", example = "95.0")
    private BigDecimal deliveryOnTimeRate;
    @Schema(description = "交付延迟次数", example = "3")
    private Integer deliveryDelayCount;
    @Schema(description = "价格评分(百分制)", example = "80")
    private BigDecimal priceScore;
    @Schema(description = "服务评分(百分制)", example = "88")
    private BigDecimal serviceScore;
    @Schema(description = "服务响应速度(小时)", example = "2")
    private Integer serviceResponseSpeed;
    @Schema(description = "备注", example = "本季度表现良好")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/SrmStatisticsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.srm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.srm.controller.admin.statistics.vo.SrmTenderStatisticsRespVO;
import cn.iocoder.yudao.module.srm.service.statistics.SrmStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM ç»Ÿè®¡åˆ†æž")
@RestController
@RequestMapping("/srm/statistics")
@Validated
public class SrmStatisticsController {
    @Resource
    private SrmStatisticsService statisticsService;
    @GetMapping("/tender")
    @Operation(summary = "获取招标统计数据")
    @PreAuthorize("@ss.hasPermission('srm:statistics:query')")
    public CommonResult<SrmTenderStatisticsRespVO> getTenderStatistics(@RequestParam("year") String year) {
        return success(statisticsService.getTenderStatistics(year));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/vo/SrmTenderStatisticsRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.srm.controller.admin.statistics.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM æ‹›æ ‡ç»Ÿè®¡ Response VO")
@Data
public class SrmTenderStatisticsRespVO {
    @Schema(description = "招标总数", example = "100")
    private Long totalTenderCount;
    @Schema(description = "招标总金额", example = "50000000")
    private BigDecimal totalTenderAmount;
    @Schema(description = "定标总金额", example = "48000000")
    private BigDecimal totalAwardAmount;
    @Schema(description = "节约金额", example = "2000000")
    private BigDecimal savedAmount;
    @Schema(description = "中标率", example = "85.5")
    private BigDecimal winRate;
    @Schema(description = "参与供应厂商数", example = "50")
    private Long supplierCount;
    @Schema(description = "统计周期", example = "2024")
    private String period;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierCategoryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCategoryDO;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM ä¾›åº”商分类")
@RestController
@RequestMapping("/srm/supplier-category")
@Validated
public class SrmSupplierCategoryController {
    @Resource
    private SrmSupplierCategoryService supplierCategoryService;
    @PostMapping("/assign")
    @Operation(summary = "分配供应商分类")
    @PreAuthorize("@ss.hasPermission('srm:supplier:update')")
    public CommonResult<Boolean> assignCategory(@RequestParam("supplierId") Long supplierId,
                                                @RequestParam("categoryId") Long categoryId,
                                                @RequestParam("categoryName") String categoryName) {
        supplierCategoryService.assignCategory(supplierId, categoryId, categoryName);
        return success(true);
    }
    @DeleteMapping("/remove")
    @Operation(summary = "移除供应商分类")
    @PreAuthorize("@ss.hasPermission('srm:supplier:update')")
    public CommonResult<Boolean> removeCategory(@RequestParam("id") Long id) {
        supplierCategoryService.removeCategory(id);
        return success(true);
    }
    @GetMapping("/list-by-supplier")
    @Operation(summary = "根据供应商ID获取分类列表")
    @PreAuthorize("@ss.hasPermission('srm:supplier:query')")
    public CommonResult<List<SrmSupplierCategoryDO>> getCategoryList(@RequestParam("supplierId") Long supplierId) {
        return success(supplierCategoryService.getCategoryListBySupplierId(supplierId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierContactController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierContactService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM ä¾›åº”商联系人")
@RestController
@RequestMapping("/srm/supplier-contact")
@Validated
public class SrmSupplierContactController {
    @Resource
    private SrmSupplierContactService contactService;
    @PostMapping("/create")
    @Operation(summary = "创建联系人")
    @PreAuthorize("@ss.hasPermission('srm:supplier-contact:create')")
    public CommonResult<Long> createContact(@Valid @RequestBody SrmSupplierContactSaveReqVO createReqVO) {
        return success(contactService.createContact(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新联系人")
    @PreAuthorize("@ss.hasPermission('srm:supplier-contact:update')")
    public CommonResult<Boolean> updateContact(@Valid @RequestBody SrmSupplierContactSaveReqVO updateReqVO) {
        contactService.updateContact(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除联系人")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-contact:delete')")
    public CommonResult<Boolean> deleteContact(@RequestParam("id") Long id) {
        contactService.deleteContact(id);
        return success(true);
    }
    @GetMapping("/list-by-supplier")
    @Operation(summary = "获取供应商联系人列表")
    @Parameter(name = "supplierId", description = "供应商ID", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier-contact:query')")
    public CommonResult<List<SrmSupplierContactRespVO>> getContactList(@RequestParam("supplierId") Long supplierId) {
        List<SrmSupplierContactDO> list = contactService.getContactListBySupplierId(supplierId);
        return success(BeanUtils.toBean(list, SrmSupplierContactRespVO.class));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM ä¾›åº”商")
@RestController
@RequestMapping("/srm/supplier")
@Validated
public class SrmSupplierController {
    @Resource
    private SrmSupplierService supplierService;
    @PostMapping("/create")
    @Operation(summary = "创建供应商")
    @PreAuthorize("@ss.hasPermission('srm:supplier:create')")
    public CommonResult<Long> createSupplier(@Valid @RequestBody SrmSupplierSaveReqVO createReqVO) {
        return success(supplierService.createSupplier(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新供应商")
    @PreAuthorize("@ss.hasPermission('srm:supplier:update')")
    public CommonResult<Boolean> updateSupplier(@Valid @RequestBody SrmSupplierSaveReqVO updateReqVO) {
        supplierService.updateSupplier(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除供应商")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier:delete')")
    public CommonResult<Boolean> deleteSupplier(@RequestParam("id") Long id) {
        supplierService.deleteSupplier(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取供应商详情")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:supplier:query')")
    public CommonResult<SrmSupplierRespVO> getSupplier(@RequestParam("id") Long id) {
        SrmSupplierDO supplier = supplierService.getSupplier(id);
        return success(BeanUtils.toBean(supplier, SrmSupplierRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "供应商分页查询")
    @PreAuthorize("@ss.hasPermission('srm:supplier:query')")
    public CommonResult<PageResult<SrmSupplierRespVO>> getSupplierPage(@Valid SrmSupplierPageReqVO pageReqVO) {
        PageResult<SrmSupplierDO> pageResult = supplierService.getSupplierPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, SrmSupplierRespVO.class));
    }
    @GetMapping("/simple-list")
    @Operation(summary = "获取供应商精简列表(仅返回已通过准入审批的供应商)")
    public CommonResult<List<SrmSupplierRespVO>> getSupplierSimpleList() {
        List<SrmSupplierDO> list = supplierService.getSupplierSimpleList();
        return success(BeanUtils.toBean(list, SrmSupplierRespVO.class));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出供应商 Excel")
    @PreAuthorize("@ss.hasPermission('srm:supplier:export')")
    public void exportSupplierExcel(@Valid SrmSupplierPageReqVO pageReqVO, HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<SrmSupplierDO> list = supplierService.getSupplierPage(pageReqVO).getList();
        ExcelUtils.write(response, "供应商数据.xlsx", "供应商", SrmSupplierRespVO.class, BeanUtils.toBean(list, SrmSupplierRespVO.class));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierContactRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - ä¾›åº”商联系人 Response VO")
@Data
public class SrmSupplierContactRespVO {
    @Schema(description = "联系人ID")
    private Long id;
    @Schema(description = "供应商ID")
    private Long supplierId;
    @Schema(description = "联系人姓名")
    private String name;
    @Schema(description = "职位")
    private String position;
    @Schema(description = "手机号码")
    private String mobile;
    @Schema(description = "固定电话")
    private String telephone;
    @Schema(description = "邮箱")
    private String email;
    @Schema(description = "微信号")
    private String wechat;
    @Schema(description = "是否默认联系人")
    private Boolean isDefault;
    @Schema(description = "排序")
    private Integer sort;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierContactSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - ä¾›åº”商联系人创建/更新 Request VO")
@Data
public class SrmSupplierContactSaveReqVO {
    @Schema(description = "联系人ID")
    private Long id;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "联系人姓名", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "联系人姓名不能为空")
    private String name;
    @Schema(description = "职位")
    private String position;
    @Schema(description = "手机号码")
    private String mobile;
    @Schema(description = "固定电话")
    private String telephone;
    @Schema(description = "邮箱")
    private String email;
    @Schema(description = "微信号")
    private String wechat;
    @Schema(description = "是否默认联系人")
    private Boolean isDefault;
    @Schema(description = "排序")
    private Integer sort;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - ä¾›åº”商分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmSupplierPageReqVO extends PageParam {
    @Schema(description = "供应商编码")
    private String code;
    @Schema(description = "供应商名称")
    private String name;
    @Schema(description = "供应商类型")
    private String type;
    @Schema(description = "状态")
    private Integer status;
    @Schema(description = "联系人")
    private String contactName;
    @Schema(description = "联系电话")
    private String contactMobile;
    @Schema(description = "仅显示已通过准入审批的供应商")
    private Boolean approved;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - ä¾›åº”商 Response VO")
@Data
public class SrmSupplierRespVO {
    @Schema(description = "供应商ID")
    private Long id;
    @Schema(description = "供应商编码")
    private String code;
    @Schema(description = "供应商名称")
    private String name;
    @Schema(description = "供应商简称")
    private String shortName;
    @Schema(description = "供应商类型")
    private String type;
    @Schema(description = "企业性质")
    private String companyNature;
    @Schema(description = "注册资金(万元)")
    private BigDecimal registeredCapital;
    @Schema(description = "营业执照编号")
    private String businessLicenseNo;
    @Schema(description = "税号")
    private String taxNo;
    @Schema(description = "法人代表")
    private String legalPerson;
    @Schema(description = "公司地址")
    private String address;
    @Schema(description = "公司网址")
    private String website;
    @Schema(description = "联系人")
    private String contactName;
    @Schema(description = "联系电话")
    private String contactMobile;
    @Schema(description = "邮箱")
    private String contactEmail;
    @Schema(description = "开户银行")
    private String bankName;
    @Schema(description = "银行账号")
    private String bankAccount;
    @Schema(description = "状态(0启用 1禁用)")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - ä¾›åº”商创建/更新 Request VO")
@Data
public class SrmSupplierSaveReqVO {
    @Schema(description = "供应商ID", example = "1")
    private Long id;
    @Schema(description = "供应商编码", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "供应商编码不能为空")
    private String code;
    @Schema(description = "供应商名称", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "供应商名称不能为空")
    private String name;
    @Schema(description = "供应商简称")
    private String shortName;
    @Schema(description = "供应商类型")
    private String type;
    @Schema(description = "企业性质")
    private String companyNature;
    @Schema(description = "注册资金(万元)")
    private BigDecimal registeredCapital;
    @Schema(description = "营业执照编号")
    private String businessLicenseNo;
    @Schema(description = "税号")
    private String taxNo;
    @Schema(description = "法人代表")
    private String legalPerson;
    @Schema(description = "公司地址")
    private String address;
    @Schema(description = "公司网址")
    private String website;
    @Schema(description = "联系人")
    private String contactName;
    @Schema(description = "联系电话")
    private String contactMobile;
    @Schema(description = "邮箱")
    private String contactEmail;
    @Schema(description = "开户银行")
    private String bankName;
    @Schema(description = "银行账号")
    private String bankAccount;
    @Schema(description = "状态(0启用 1禁用)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "状态不能为空")
    private Integer status;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
import cn.iocoder.yudao.module.srm.service.tender.SrmBidAwardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM å®šæ ‡ç®¡ç†")
@RestController
@RequestMapping("/srm/bid-award")
@Validated
public class SrmBidAwardController {
    @Resource
    private SrmBidAwardService bidAwardService;
    @PostMapping("/create")
    @Operation(summary = "创建定标")
    @PreAuthorize("@ss.hasPermission('srm:bid-award:create')")
    public CommonResult<Long> createAward(@Valid @RequestBody SrmBidAwardSaveReqVO createReqVO) {
        return success(bidAwardService.createAward(createReqVO));
    }
    @GetMapping("/get")
    @Operation(summary = "获取定标详情")
    @PreAuthorize("@ss.hasPermission('srm:bid-award:query')")
    public CommonResult<SrmBidAwardRespVO> getAward(@RequestParam("id") Long id) {
        return success(BeanUtils.toBean(bidAwardService.getAward(id), SrmBidAwardRespVO.class));
    }
    @PutMapping("/approve")
    @Operation(summary = "审批定标")
    @Parameter(name = "id", description = "定标ID", required = true)
    @PreAuthorize("@ss.hasPermission('srm:bid-award:update')")
    public CommonResult<Boolean> approveAward(@RequestParam("id") Long id) {
        bidAwardService.approveAward(id);
        return success(true);
    }
    @PutMapping("/generate-purchase-order/{id}")
    @Operation(summary = "生成采购订单")
    @Parameter(name = "id", description = "定标ID", required = true)
    @PreAuthorize("@ss.hasPermission('srm:bid-award:update')")
    public CommonResult<Boolean> generatePurchaseOrder(@PathVariable("id") Long id) {
        bidAwardService.generatePurchaseOrder(id);
        return success(true);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
import cn.iocoder.yudao.module.srm.service.tender.SrmBidEvaluationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM è¯„标管理")
@RestController
@RequestMapping("/srm/bid-evaluation")
@Validated
public class SrmBidEvaluationController {
    @Resource
    private SrmBidEvaluationService bidEvaluationService;
    @PostMapping("/create")
    @Operation(summary = "创建评标记录")
    @PreAuthorize("@ss.hasPermission('srm:bid-evaluation:create')")
    public CommonResult<Long> createEvaluation(@Valid @RequestBody SrmBidEvaluationSaveReqVO createReqVO) {
        return success(bidEvaluationService.createEvaluation(createReqVO));
    }
    @GetMapping("/list-by-project")
    @Operation(summary = "按招标项目获取评标列表")
    @PreAuthorize("@ss.hasPermission('srm:bid-evaluation:query')")
    public CommonResult<List<SrmBidEvaluationRespVO>> getEvaluationList(@RequestParam("tenderProjectId") Long tenderProjectId) {
        return success(BeanUtils.toBean(bidEvaluationService.getEvaluationList(tenderProjectId), SrmBidEvaluationRespVO.class));
    }
    @PutMapping("/calculate-ranking")
    @Operation(summary = "计算排名")
    @PreAuthorize("@ss.hasPermission('srm:bid-evaluation:update')")
    public CommonResult<Boolean> calculateRanking(@RequestParam("tenderProjectId") Long tenderProjectId) {
        bidEvaluationService.calculateRanking(tenderProjectId);
        return success(true);
    }
    // ========== å¼€æ ‡ ==========
    @PostMapping("/bid-open/create")
    @Operation(summary = "开标")
    @PreAuthorize("@ss.hasPermission('srm:bid-open:create')")
    public CommonResult<Long> createBidOpen(@RequestParam("tenderProjectId") Long tenderProjectId) {
        return success(bidEvaluationService.createBidOpen(tenderProjectId));
    }
    @GetMapping("/bid-open/get")
    @Operation(summary = "获取开标记录")
    @PreAuthorize("@ss.hasPermission('srm:bid-open:query')")
    public CommonResult<SrmBidOpenRespVO> getBidOpen(@RequestParam("id") Long id) {
        return success(BeanUtils.toBean(bidEvaluationService.getBidOpen(id), SrmBidOpenRespVO.class));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,119 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import cn.iocoder.yudao.module.srm.service.tender.SrmTenderBidService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM æŠ•标管理")
@RestController
@RequestMapping("/srm/tender-bid")
@Validated
public class SrmTenderBidController {
    @Resource
    private SrmTenderBidService tenderBidService;
    @Resource
    private SrmSupplierService supplierService;
    // ========== æŠ•标管理 ==========
    @PostMapping("/create")
    @Operation(summary = "创建投标")
    @PreAuthorize("@ss.hasPermission('srm:tender-bid:create')")
    public CommonResult<Long> createBid(@Valid @RequestBody SrmTenderBidSaveReqVO createReqVO) {
        return success(tenderBidService.createBid(createReqVO));
    }
    @GetMapping("/get")
    @Operation(summary = "获取投标详情")
    @PreAuthorize("@ss.hasPermission('srm:tender-bid:query')")
    public CommonResult<SrmTenderBidRespVO> getBid(@RequestParam("id") Long id) {
        SrmTenderBidRespVO resp = BeanUtils.toBean(tenderBidService.getBid(id), SrmTenderBidRespVO.class);
        enrichSupplierName(resp);
        return success(resp);
    }
    @GetMapping("/list-by-project")
    @Operation(summary = "按招标项目获取投标列表")
    @PreAuthorize("@ss.hasPermission('srm:tender-bid:query')")
    public CommonResult<List<SrmTenderBidRespVO>> getBidListByProject(@RequestParam("tenderProjectId") Long tenderProjectId) {
        List<SrmTenderBidRespVO> list = BeanUtils.toBean(tenderBidService.getBidListByProject(tenderProjectId), SrmTenderBidRespVO.class);
        enrichSupplierNames(list);
        return success(list);
    }
    @PutMapping("/withdraw")
    @Operation(summary = "撤标")
    @Parameter(name = "id", description = "投标ID", required = true)
    @PreAuthorize("@ss.hasPermission('srm:tender-bid:update')")
    public CommonResult<Boolean> withdrawBid(@RequestParam("id") Long id) {
        tenderBidService.withdrawBid(id);
        return success(true);
    }
    // ========== ä¾›åº”商报价 ==========
    @PostMapping("/quote/create")
    @Operation(summary = "创建供应商报价")
    @PreAuthorize("@ss.hasPermission('srm:supplier-quote:create')")
    public CommonResult<Long> createQuote(@Valid @RequestBody SrmSupplierQuoteSaveReqVO createReqVO) {
        return success(tenderBidService.createQuote(createReqVO));
    }
    @PutMapping("/quote/update")
    @Operation(summary = "更新供应商报价")
    @PreAuthorize("@ss.hasPermission('srm:supplier-quote:update')")
    public CommonResult<Boolean> updateQuote(@Valid @RequestBody SrmSupplierQuoteSaveReqVO updateReqVO) {
        tenderBidService.updateQuote(updateReqVO);
        return success(true);
    }
    @GetMapping("/quote/list-by-bid")
    @Operation(summary = "按投标获取报价列表")
    @PreAuthorize("@ss.hasPermission('srm:supplier-quote:query')")
    public CommonResult<List<SrmSupplierQuoteRespVO>> getQuoteListByBid(@RequestParam("bidId") Long bidId) {
        return success(BeanUtils.toBean(tenderBidService.getQuoteList(bidId), SrmSupplierQuoteRespVO.class));
    }
    // ========== å…³è”数据填充 ==========
    private void enrichSupplierNames(List<SrmTenderBidRespVO> list) {
        if (list == null || list.isEmpty()) return;
        Set<Long> supplierIds = CollectionUtils.convertSet(list, SrmTenderBidRespVO::getSupplierId);
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
        list.forEach(vo -> {
            SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId());
            if (supplier != null) {
                vo.setSupplierName(supplier.getName());
            }
        });
    }
    private void enrichSupplierName(SrmTenderBidRespVO vo) {
        if (vo == null || vo.getSupplierId() == null) return;
        SrmSupplierDO supplier = supplierService.getSupplier(vo.getSupplierId());
        if (supplier != null) {
            vo.setSupplierName(supplier.getName());
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
import cn.iocoder.yudao.module.srm.service.tender.SrmTenderProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM æ‹›æ ‡é¡¹ç›®")
@RestController
@RequestMapping("/srm/tender-project")
@Validated
public class SrmTenderProjectController {
    @Resource
    private SrmTenderProjectService tenderProjectService;
    @PostMapping("/create")
    @Operation(summary = "创建招标项目")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:create')")
    public CommonResult<Long> createTenderProject(@Valid @RequestBody SrmTenderProjectSaveReqVO createReqVO) {
        return success(tenderProjectService.createTenderProject(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新招标项目")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> updateTenderProject(@Valid @RequestBody SrmTenderProjectSaveReqVO updateReqVO) {
        tenderProjectService.updateTenderProject(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除招标项目")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:delete')")
    public CommonResult<Boolean> deleteTenderProject(@RequestParam("id") Long id) {
        tenderProjectService.deleteTenderProject(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取招标项目详情")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:query')")
    public CommonResult<SrmTenderProjectRespVO> getTenderProject(@RequestParam("id") Long id) {
        return success(BeanUtils.toBean(tenderProjectService.getTenderProject(id), SrmTenderProjectRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获取招标项目分页")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:query')")
    public CommonResult<PageResult<SrmTenderProjectRespVO>> getTenderProjectPage(@Valid SrmTenderProjectPageReqVO pageReqVO) {
        return success(BeanUtils.toBean(tenderProjectService.getTenderProjectPage(pageReqVO), SrmTenderProjectRespVO.class));
    }
    @PutMapping("/publish")
    @Operation(summary = "发布招标")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> publishTender(@RequestParam("id") Long id) {
        tenderProjectService.publishTender(id);
        return success(true);
    }
    @PutMapping("/close")
    @Operation(summary = "关闭招标")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> closeTender(@RequestParam("id") Long id) {
        tenderProjectService.closeTender(id);
        return success(true);
    }
    @PutMapping("/start-bidding")
    @Operation(summary = "开始投标")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> startBidding(@RequestParam("id") Long id) {
        tenderProjectService.startBidding(id);
        return success(true);
    }
    @PutMapping("/start-evaluation")
    @Operation(summary = "开始评标")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> startEvaluation(@RequestParam("id") Long id) {
        tenderProjectService.startEvaluation(id);
        return success(true);
    }
    // ========== ç‰©æ–™å­è¡¨ ==========
    @PostMapping("/material/create")
    @Operation(summary = "创建招标物料")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:create')")
    public CommonResult<Long> createMaterial(@Valid @RequestBody SrmTenderMaterialSaveReqVO createReqVO) {
        tenderProjectService.createMaterial(createReqVO);
        return success(createReqVO.getId());
    }
    @PutMapping("/material/update")
    @Operation(summary = "更新招标物料")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:update')")
    public CommonResult<Boolean> updateMaterial(@Valid @RequestBody SrmTenderMaterialSaveReqVO updateReqVO) {
        tenderProjectService.updateMaterial(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/material/delete")
    @Operation(summary = "删除招标物料")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:delete')")
    public CommonResult<Boolean> deleteMaterial(@RequestParam("id") Long id) {
        tenderProjectService.deleteMaterial(id);
        return success(true);
    }
    @GetMapping("/material/list")
    @Operation(summary = "获取招标物料列表")
    @PreAuthorize("@ss.hasPermission('srm:tender-project:query')")
    public CommonResult<List<SrmTenderMaterialRespVO>> getMaterialList(@RequestParam("tenderProjectId") Long tenderProjectId) {
        return success(BeanUtils.toBean(tenderProjectService.getMaterialList(tenderProjectId), SrmTenderMaterialRespVO.class));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM å®šæ ‡ Response VO")
@Data
public class SrmBidAwardRespVO {
    @Schema(description = "定标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "投标ID", example = "1")
    private Long bidId;
    @Schema(description = "定标编号", example = "AWARD2024010001")
    private String awardNo;
    @Schema(description = "定标状态", example = "0")
    private Integer awardStatus;
    @Schema(description = "定标金额", example = "500000")
    private BigDecimal awardAmount;
    @Schema(description = "定标时间")
    private LocalDateTime awardTime;
    @Schema(description = "通知书文件名", example = "award_notice.pdf")
    private String noticeFileName;
    @Schema(description = "通知书文件地址", example = "https://example.com/files/xxx.pdf")
    private String noticeFileUrl;
    @Schema(description = "采购订单ID", example = "1")
    private Long purchaseOrderId;
    @Schema(description = "采购订单编号", example = "PO2024010001")
    private String purchaseOrderNo;
    @Schema(description = "审批人ID", example = "1")
    private Long approverId;
    @Schema(description = "审批时间")
    private LocalDateTime approvalTime;
    @Schema(description = "备注", example = "综合评标第一名")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM å®šæ ‡åˆ›å»º/更新 Request VO")
@Data
public class SrmBidAwardSaveReqVO {
    @Schema(description = "定标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标项目ID不能为空")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "投标ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "投标ID不能为空")
    private Long bidId;
    @Schema(description = "定标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "AWARD2024010001")
    @NotEmpty(message = "定标编号不能为空")
    private String awardNo;
    @Schema(description = "定标状态", example = "0")
    private Integer awardStatus;
    @Schema(description = "定标金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "500000")
    @NotNull(message = "定标金额不能为空")
    private BigDecimal awardAmount;
    @Schema(description = "定标时间")
    private LocalDateTime awardTime;
    @Schema(description = "通知书文件名", example = "award_notice.pdf")
    private String noticeFileName;
    @Schema(description = "通知书文件地址", example = "https://example.com/files/xxx.pdf")
    private String noticeFileUrl;
    @Schema(description = "备注", example = "综合评标第一名")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM è¯„æ ‡ Response VO")
@Data
public class SrmBidEvaluationRespVO {
    @Schema(description = "评标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "投标ID", example = "1")
    private Long bidId;
    @Schema(description = "价格评分(百分制)", example = "85")
    private BigDecimal priceScore;
    @Schema(description = "技术评分(百分制)", example = "90")
    private BigDecimal techScore;
    @Schema(description = "交付评分(百分制)", example = "80")
    private BigDecimal deliveryScore;
    @Schema(description = "服务评分(百分制)", example = "88")
    private BigDecimal serviceScore;
    @Schema(description = "综合评分", example = "87.5")
    private BigDecimal compositeScore;
    @Schema(description = "排名", example = "1")
    private Integer ranking;
    @Schema(description = "评委ID", example = "1")
    private Long evaluatorId;
    @Schema(description = "评委名称", example = "王五")
    private String evaluatorName;
    @Schema(description = "评标意见", example = "技术方案优秀,价格合理")
    private String evaluationOpinion;
    @Schema(description = "评价时间")
    private LocalDateTime evaluationTime;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM è¯„标创建/更新 Request VO")
@Data
public class SrmBidEvaluationSaveReqVO {
    @Schema(description = "评标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标项目ID不能为空")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "投标ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "投标ID不能为空")
    private Long bidId;
    @Schema(description = "价格评分(百分制)", example = "85")
    private BigDecimal priceScore;
    @Schema(description = "技术评分(百分制)", example = "90")
    private BigDecimal techScore;
    @Schema(description = "交付评分(百分制)", example = "80")
    private BigDecimal deliveryScore;
    @Schema(description = "服务评分(百分制)", example = "88")
    private BigDecimal serviceScore;
    @Schema(description = "评委ID", example = "1")
    private Long evaluatorId;
    @Schema(description = "评委名称", example = "王五")
    private String evaluatorName;
    @Schema(description = "评标意见", example = "技术方案优秀,价格合理")
    private String evaluationOpinion;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidOpenRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM å¼€æ ‡ Response VO")
@Data
public class SrmBidOpenRespVO {
    @Schema(description = "开标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "开标时间")
    private LocalDateTime openTime;
    @Schema(description = "开标人ID", example = "1")
    private Long openPersonId;
    @Schema(description = "开标人名称", example = "张三")
    private String openPersonName;
    @Schema(description = "开标记录", example = "共有3家供应商参与投标")
    private String openRecord;
    @Schema(description = "附件名称", example = "open_record.pdf")
    private String attachmentName;
    @Schema(description = "附件地址", example = "https://example.com/files/xxx.pdf")
    private String attachmentUrl;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM ä¾›åº”商报价 Response VO")
@Data
public class SrmSupplierQuoteRespVO {
    @Schema(description = "报价ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "投标ID", example = "1")
    private Long bidId;
    @Schema(description = "招标物料ID", example = "1")
    private Long tenderMaterialId;
    @Schema(description = "报价价格", example = "5000")
    private BigDecimal quotePrice;
    @Schema(description = "税率", example = "13")
    private BigDecimal taxRate;
    @Schema(description = "交货周期(天)", example = "30")
    private Integer deliveryCycle;
    @Schema(description = "付款条件", example = "货到30天付款")
    private String paymentTerms;
    @Schema(description = "质保期(月)", example = "12")
    private Integer warrantyPeriod;
    @Schema(description = "备注", example = "含运费")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM ä¾›åº”商报价创建/更新 Request VO")
@Data
public class SrmSupplierQuoteSaveReqVO {
    @Schema(description = "报价ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "投标ID", example = "1")
    private Long bidId;
    @Schema(description = "招标物料ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标物料ID不能为空")
    private Long tenderMaterialId;
    @Schema(description = "报价价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000")
    @NotNull(message = "报价价格不能为空")
    private BigDecimal quotePrice;
    @Schema(description = "税率", example = "13")
    private BigDecimal taxRate;
    @Schema(description = "交货周期(天)", example = "30")
    private Integer deliveryCycle;
    @Schema(description = "付款条件", example = "货到30天付款")
    private String paymentTerms;
    @Schema(description = "质保期(月)", example = "12")
    private Integer warrantyPeriod;
    @Schema(description = "备注", example = "含运费")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM æŠ•æ ‡ Response VO")
@Data
public class SrmTenderBidRespVO {
    @Schema(description = "投标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "xxx供应商")
    private String supplierName;
    @Schema(description = "投标编号", example = "BID2024010001")
    private String bidNo;
    @Schema(description = "投标状态", example = "0")
    private Integer bidStatus;
    @Schema(description = "投标时间")
    private LocalDateTime bidTime;
    @Schema(description = "投标总金额", example = "500000")
    private BigDecimal bidTotalAmount;
    @Schema(description = "投标文件名", example = "bid_file.pdf")
    private String bidFileName;
    @Schema(description = "投标文件地址", example = "https://example.com/files/xxx.pdf")
    private String bidFileUrl;
    @Schema(description = "撤标原因", example = "技术参数变更")
    private String withdrawReason;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM æŠ•标创建/更新 Request VO")
@Data
public class SrmTenderBidSaveReqVO {
    @Schema(description = "投标ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标项目ID不能为空")
    private Long tenderProjectId;
    @Schema(description = "供应商ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "供应商ID不能为空")
    private Long supplierId;
    @Schema(description = "投标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "BID2024010001")
    @NotEmpty(message = "投标编号不能为空")
    private String bidNo;
    @Schema(description = "投标状态", example = "0")
    private Integer bidStatus;
    @Schema(description = "投标时间")
    private LocalDateTime bidTime;
    @Schema(description = "投标总金额", example = "500000")
    private BigDecimal bidTotalAmount;
    @Schema(description = "投标文件名", example = "bid_file.pdf")
    private String bidFileName;
    @Schema(description = "投标文件地址", example = "https://example.com/files/xxx.pdf")
    private String bidFileUrl;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM æ‹›æ ‡ç‰©æ–™ Response VO")
@Data
public class SrmTenderMaterialRespVO {
    @Schema(description = "物料ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", example = "1")
    private Long tenderProjectId;
    @Schema(description = "产品ID", example = "1")
    private Long productId;
    @Schema(description = "产品编号", example = "ITEM-0001")
    private String productCode;
    @Schema(description = "产品名称", example = "Q235钢材")
    private String productName;
    @Schema(description = "产品规格", example = "10mm")
    private String productSpec;
    @Schema(description = "数量", example = "1000")
    private BigDecimal quantity;
    @Schema(description = "单位", example = "吨")
    private String unit;
    @Schema(description = "技术要求", example = "符合GB/T 700-2006标准")
    private String techRequirement;
    @Schema(description = "预估单价", example = "5000")
    private BigDecimal estimatedPrice;
    @Schema(description = "排序", example = "1")
    private Integer sort;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - SRM æ‹›æ ‡ç‰©æ–™åˆ›å»º/更新 Request VO")
@Data
public class SrmTenderMaterialSaveReqVO {
    @Schema(description = "物料ID", example = "1")
    private Long id;
    @Schema(description = "招标项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标项目ID不能为空")
    private Long tenderProjectId;
    @Schema(description = "产品ID", example = "1")
    private Long productId;
    @Schema(description = "产品编号", example = "ITEM-0001")
    private String productCode;
    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "Q235钢材")
    @NotEmpty(message = "产品名称不能为空")
    private String productName;
    @Schema(description = "产品规格", example = "10mm")
    private String productSpec;
    @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
    @NotNull(message = "数量不能为空")
    private BigDecimal quantity;
    @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "吨")
    @NotEmpty(message = "单位不能为空")
    private String unit;
    @Schema(description = "技术要求", example = "符合GB/T 700-2006标准")
    private String techRequirement;
    @Schema(description = "预估单价", example = "5000")
    private BigDecimal estimatedPrice;
    @Schema(description = "排序", example = "1")
    private Integer sort;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - SRM æ‹›æ ‡é¡¹ç›®åˆ†é¡µæŸ¥è¯¢ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmTenderProjectPageReqVO extends PageParam {
    @Schema(description = "招标编号", example = "ZB2024010001")
    private String tenderNo;
    @Schema(description = "招标名称", example = "2024年钢材采购招标")
    private String tenderName;
    @Schema(description = "招标类型", example = "1")
    private Integer tenderType;
    @Schema(description = "招标状态", example = "0")
    private Integer tenderStatus;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM æ‹›æ ‡é¡¹ç›® Response VO")
@Data
public class SrmTenderProjectRespVO {
    @Schema(description = "项目ID", example = "1")
    private Long id;
    @Schema(description = "招标编号", example = "ZB2024010001")
    private String tenderNo;
    @Schema(description = "招标名称", example = "2024年钢材采购招标")
    private String tenderName;
    @Schema(description = "招标类型", example = "1")
    private Integer tenderType;
    @Schema(description = "采购组织", example = "采购部")
    private String purchaseOrg;
    @Schema(description = "项目负责人ID", example = "1")
    private Long projectLeaderId;
    @Schema(description = "项目负责人名称", example = "李四")
    private String projectLeaderName;
    @Schema(description = "招标状态", example = "0")
    private Integer tenderStatus;
    @Schema(description = "预算金额", example = "1000000")
    private BigDecimal budgetAmount;
    @Schema(description = "开始时间")
    private LocalDateTime startTime;
    @Schema(description = "结束时间")
    private LocalDateTime endTime;
    @Schema(description = "开标时间")
    private LocalDateTime bidOpenTime;
    @Schema(description = "招标描述", example = "本次招标采购钢材一批")
    private String tenderDesc;
    @Schema(description = "资质要求", example = "需具备ISO9001认证")
    private String qualificationRequirements;
    @Schema(description = "采购申请ID", example = "1")
    private Long purchaseRequestId;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "定标ID", example = "1")
    private Long awardId;
    @Schema(description = "采购订单号", example = "PO2024010001")
    private String purchaseOrderNo;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - SRM æ‹›æ ‡é¡¹ç›®åˆ›å»º/更新 Request VO")
@Data
public class SrmTenderProjectSaveReqVO {
    @Schema(description = "项目ID", example = "1")
    private Long id;
    @Schema(description = "招标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ZB2024010001")
    @NotEmpty(message = "招标编号不能为空")
    private String tenderNo;
    @Schema(description = "招标名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024年钢材采购招标")
    @NotEmpty(message = "招标名称不能为空")
    private String tenderName;
    @Schema(description = "招标类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "招标类型不能为空")
    private Integer tenderType;
    @Schema(description = "采购组织", example = "采购部")
    private String purchaseOrg;
    @Schema(description = "项目负责人ID", example = "1")
    private Long projectLeaderId;
    @Schema(description = "项目负责人名称", example = "李四")
    private String projectLeaderName;
    @Schema(description = "招标状态", example = "0")
    private Integer tenderStatus;
    @Schema(description = "预算金额", example = "1000000")
    private BigDecimal budgetAmount;
    @Schema(description = "开始时间")
    private LocalDateTime startTime;
    @Schema(description = "结束时间")
    private LocalDateTime endTime;
    @Schema(description = "开标时间")
    private LocalDateTime bidOpenTime;
    @Schema(description = "招标描述", example = "本次招标采购钢材一批")
    private String tenderDesc;
    @Schema(description = "资质要求", example = "需具备ISO9001认证")
    private String qualificationRequirements;
    @Schema(description = "采购申请ID", example = "1")
    private Long purchaseRequestId;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidAwardDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@TableName("srm_bid_award")
@KeySequence("srm_bid_award_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmBidAwardDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private Long supplierId;
    private Long bidId;
    private String awardNo;
    private Integer awardStatus;
    private BigDecimal awardAmount;
    private LocalDateTime awardTime;
    private String noticeFileName;
    private String noticeFileUrl;
    private Long purchaseOrderId;
    private String purchaseOrderNo;
    private Long approverId;
    private LocalDateTime approvalTime;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidEvaluationDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@TableName("srm_bid_evaluation")
@KeySequence("srm_bid_evaluation_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmBidEvaluationDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private Long supplierId;
    private Long bidId;
    private BigDecimal priceScore;
    private BigDecimal techScore;
    private BigDecimal deliveryScore;
    private BigDecimal serviceScore;
    private BigDecimal compositeScore;
    private Integer ranking;
    private Long evaluatorId;
    private String evaluatorName;
    private String evaluationOpinion;
    private LocalDateTime evaluationTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidOpenDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
@TableName("srm_bid_open")
@KeySequence("srm_bid_open_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmBidOpenDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private LocalDateTime openTime;
    private Long openPersonId;
    private String openPersonName;
    private String openRecord;
    private String attachmentName;
    private String attachmentUrl;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ä¾›åº”商准入申请 DO
 */
@TableName("srm_supplier_apply")
@KeySequence("srm_supplier_apply_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierApplyDO extends BaseDO {
    @TableId
    private Long id;
    private String applyNo;
    private Long supplierId;
    private String supplierName;
    private String supplierType;
    private String companyNature;
    private BigDecimal registeredCapital;
    private String businessLicenseNo;
    private String taxNo;
    private String address;
    private String contactName;
    private String contactMobile;
    private String contactEmail;
    private Long applyUserId;
    private String applyUserName;
    private LocalDateTime applyTime;
    private Integer applyStatus;
    private Long purchaseReviewerId;
    private LocalDateTime purchaseReviewTime;
    private String purchaseReviewOpinion;
    private Long qualityReviewerId;
    private LocalDateTime qualityReviewTime;
    private String qualityReviewOpinion;
    private Long financeReviewerId;
    private LocalDateTime financeReviewTime;
    private String financeReviewOpinion;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCategoryDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * ä¾›åº”商分类关联 DO
 */
@TableName("srm_supplier_category")
@KeySequence("srm_supplier_category_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierCategoryDO extends cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO {
    @TableId
    private Long id;
    private Long supplierId;
    private Long categoryId;
    private String categoryName;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCertificateDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDate;
/**
 * ä¾›åº”商资质证书 DO
 */
@TableName("srm_supplier_certificate")
@KeySequence("srm_supplier_certificate_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierCertificateDO extends BaseDO {
    @TableId
    private Long id;
    private Long supplierId;
    private Integer certificateType;
    private String certificateName;
    private String certificateNo;
    private String fileName;
    private String fileUrl;
    private String issuingAuthority;
    private LocalDate validStartDate;
    private LocalDate validEndDate;
    private Integer status;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierContactDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * ä¾›åº”商联系人 DO
 */
@TableName("srm_supplier_contact")
@KeySequence("srm_supplier_contact_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierContactDO extends BaseDO {
    @TableId
    private Long id;
    private Long supplierId;
    private String name;
    private String position;
    private String mobile;
    private String telephone;
    private String email;
    private String wechat;
    private Boolean isDefault;
    private Integer sort;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
 * SRM ä¾›åº”商主表 DO
 */
@TableName("srm_supplier")
@KeySequence("srm_supplier_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierDO extends BaseDO {
    @TableId
    private Long id;
    private String code;
    private String name;
    private String shortName;
    private String type;
    private String companyNature;
    private BigDecimal registeredCapital;
    private String businessLicenseNo;
    private String taxNo;
    private String legalPerson;
    private String address;
    private String website;
    private String contactName;
    private String contactMobile;
    private String contactEmail;
    private String bankName;
    private String bankAccount;
    private Integer status;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierEvaluationDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ä¾›åº”商绩效评价 DO
 */
@TableName("srm_supplier_evaluation")
@KeySequence("srm_supplier_evaluation_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierEvaluationDO extends BaseDO {
    @TableId
    private Long id;
    private Long supplierId;
    private String evaluationPeriod;
    private BigDecimal qualityScore;
    private BigDecimal qualityPassRate;
    private Integer qualityDefectCount;
    private BigDecimal deliveryScore;
    private BigDecimal deliveryOnTimeRate;
    private Integer deliveryDelayCount;
    private BigDecimal priceScore;
    private BigDecimal serviceScore;
    private Integer serviceResponseSpeed;
    private BigDecimal compositeScore;
    private String evaluationLevel;
    private Long evaluatorId;
    private LocalDateTime evaluationTime;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierQuoteDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
@TableName("srm_supplier_quote")
@KeySequence("srm_supplier_quote_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierQuoteDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private Long supplierId;
    private Long bidId;
    private Long tenderMaterialId;
    private BigDecimal quotePrice;
    private BigDecimal taxRate;
    private Integer deliveryCycle;
    private String paymentTerms;
    private Integer warrantyPeriod;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderBidDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@TableName("srm_tender_bid")
@KeySequence("srm_tender_bid_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmTenderBidDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private Long supplierId;
    private String bidNo;
    private Integer bidStatus;
    private LocalDateTime bidTime;
    private BigDecimal bidTotalAmount;
    private String bidFileName;
    private String bidFileUrl;
    private String withdrawReason;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderMaterialDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
@TableName("srm_tender_material")
@KeySequence("srm_tender_material_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmTenderMaterialDO extends BaseDO {
    @TableId
    private Long id;
    private Long tenderProjectId;
    private Long productId;
    private String productCode;
    private String productName;
    private String productSpec;
    private BigDecimal quantity;
    private String unit;
    private String techRequirement;
    private BigDecimal estimatedPrice;
    private Integer sort;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderProjectDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@TableName("srm_tender_project")
@KeySequence("srm_tender_project_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmTenderProjectDO extends BaseDO {
    @TableId
    private Long id;
    private String tenderNo;
    private String tenderName;
    private Integer tenderType;
    private String purchaseOrg;
    private Long projectLeaderId;
    private String projectLeaderName;
    private Integer tenderStatus;
    private BigDecimal budgetAmount;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private LocalDateTime bidOpenTime;
    private String tenderDesc;
    private String qualificationRequirements;
    private Long purchaseRequestId;
    @TableField(exist = false)
    private Long awardId;
    @TableField(exist = false)
    private String purchaseOrderNo;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidAwardMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SrmBidAwardMapper extends BaseMapperX<SrmBidAwardDO> {
    default SrmBidAwardDO selectByTenderProjectId(Long tenderProjectId) {
        return selectOne(new LambdaQueryWrapperX<SrmBidAwardDO>()
                .eq(SrmBidAwardDO::getTenderProjectId, tenderProjectId));
    }
    default List<SrmBidAwardDO> selectListByTenderProjectIds(Collection<Long> tenderProjectIds) {
        return selectList(new LambdaQueryWrapperX<SrmBidAwardDO>()
                .in(SrmBidAwardDO::getTenderProjectId, tenderProjectIds));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidEvaluationMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidEvaluationDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmBidEvaluationMapper extends BaseMapperX<SrmBidEvaluationDO> {
    default List<SrmBidEvaluationDO> selectListByTenderProjectId(Long tenderProjectId) {
        return selectList(new LambdaQueryWrapperX<SrmBidEvaluationDO>()
                .eq(SrmBidEvaluationDO::getTenderProjectId, tenderProjectId)
                .orderByAsc(SrmBidEvaluationDO::getRanking));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidOpenMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidOpenDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SrmBidOpenMapper extends BaseMapperX<SrmBidOpenDO> {
    default SrmBidOpenDO selectByTenderProjectId(Long tenderProjectId) {
        return selectOne(new LambdaQueryWrapperX<SrmBidOpenDO>()
                .eq(SrmBidOpenDO::getTenderProjectId, tenderProjectId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierApplyMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.SrmSupplierApplyPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierApplyDO;
import cn.iocoder.yudao.module.srm.enums.SupplierApplyStatusEnum;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SrmSupplierApplyMapper extends BaseMapperX<SrmSupplierApplyDO> {
    default PageResult<SrmSupplierApplyDO> selectPage(SrmSupplierApplyPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmSupplierApplyDO>()
                .likeIfPresent(SrmSupplierApplyDO::getApplyNo, reqVO.getApplyNo())
                .likeIfPresent(SrmSupplierApplyDO::getSupplierName, reqVO.getSupplierName())
                .eqIfPresent(SrmSupplierApplyDO::getApplyStatus, reqVO.getApplyStatus())
                .orderByDesc(SrmSupplierApplyDO::getId));
    }
    default boolean existsApprovedBySupplierId(Long supplierId) {
        return selectCount(new LambdaQueryWrapperX<SrmSupplierApplyDO>()
                .eq(SrmSupplierApplyDO::getSupplierId, supplierId)
                .eq(SrmSupplierApplyDO::getApplyStatus, SupplierApplyStatusEnum.APPROVED.getCode())) > 0;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCategoryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCategoryDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmSupplierCategoryMapper extends BaseMapperX<SrmSupplierCategoryDO> {
    default List<SrmSupplierCategoryDO> selectListBySupplierId(Long supplierId) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierCategoryDO>()
                .eq(SrmSupplierCategoryDO::getSupplierId, supplierId));
    }
    default void deleteBySupplierId(Long supplierId) {
        delete(new LambdaQueryWrapperX<SrmSupplierCategoryDO>()
                .eq(SrmSupplierCategoryDO::getSupplierId, supplierId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificatePageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCertificateDO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDate;
import java.util.List;
@Mapper
public interface SrmSupplierCertificateMapper extends BaseMapperX<SrmSupplierCertificateDO> {
    default PageResult<SrmSupplierCertificateDO> selectPage(SrmSupplierCertificatePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmSupplierCertificateDO>()
                .eqIfPresent(SrmSupplierCertificateDO::getSupplierId, reqVO.getSupplierId())
                .eqIfPresent(SrmSupplierCertificateDO::getCertificateType, reqVO.getCertificateType())
                .orderByDesc(SrmSupplierCertificateDO::getId));
    }
    default List<SrmSupplierCertificateDO> selectListBySupplierId(Long supplierId) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierCertificateDO>()
                .eq(SrmSupplierCertificateDO::getSupplierId, supplierId));
    }
    default List<SrmSupplierCertificateDO> selectExpiringList(LocalDate startDate, LocalDate endDate) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierCertificateDO>()
                .between(SrmSupplierCertificateDO::getValidEndDate, startDate, endDate));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierContactMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmSupplierContactMapper extends BaseMapperX<SrmSupplierContactDO> {
    default List<SrmSupplierContactDO> selectListBySupplierId(Long supplierId) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierContactDO>()
                .eq(SrmSupplierContactDO::getSupplierId, supplierId)
                .orderByAsc(SrmSupplierContactDO::getSort));
    }
    default void deleteBySupplierId(Long supplierId) {
        delete(new LambdaQueryWrapperX<SrmSupplierContactDO>()
                .eq(SrmSupplierContactDO::getSupplierId, supplierId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierEvaluationMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.SrmSupplierEvaluationPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierEvaluationDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SrmSupplierEvaluationMapper extends BaseMapperX<SrmSupplierEvaluationDO> {
    default PageResult<SrmSupplierEvaluationDO> selectPage(SrmSupplierEvaluationPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmSupplierEvaluationDO>()
                .eqIfPresent(SrmSupplierEvaluationDO::getSupplierId, reqVO.getSupplierId())
                .eqIfPresent(SrmSupplierEvaluationDO::getEvaluationPeriod, reqVO.getEvaluationPeriod())
                .orderByDesc(SrmSupplierEvaluationDO::getId));
    }
    default SrmSupplierEvaluationDO selectBySupplierAndPeriod(Long supplierId, String period) {
        return selectOne(new LambdaQueryWrapperX<SrmSupplierEvaluationDO>()
                .eq(SrmSupplierEvaluationDO::getSupplierId, supplierId)
                .eq(SrmSupplierEvaluationDO::getEvaluationPeriod, period));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SrmSupplierMapper extends BaseMapperX<SrmSupplierDO> {
    default PageResult<SrmSupplierDO> selectPage(SrmSupplierPageReqVO reqVO) {
        LambdaQueryWrapperX<SrmSupplierDO> wrapper = new LambdaQueryWrapperX<SrmSupplierDO>()
                .likeIfPresent(SrmSupplierDO::getCode, reqVO.getCode())
                .likeIfPresent(SrmSupplierDO::getName, reqVO.getName())
                .eqIfPresent(SrmSupplierDO::getType, reqVO.getType())
                .eqIfPresent(SrmSupplierDO::getStatus, reqVO.getStatus())
                .likeIfPresent(SrmSupplierDO::getContactName, reqVO.getContactName())
                .likeIfPresent(SrmSupplierDO::getContactMobile, reqVO.getContactMobile())
                .orderByDesc(SrmSupplierDO::getId);
        if (Boolean.TRUE.equals(reqVO.getApproved())) {
            wrapper.apply("id IN (SELECT supplier_id FROM srm_supplier_apply WHERE apply_status = 40 AND supplier_id IS NOT NULL AND deleted = 0)");
        }
        return selectPage(reqVO, wrapper);
    }
    default SrmSupplierDO selectByCode(String code) {
        return selectOne(SrmSupplierDO::getCode, code);
    }
    default List<SrmSupplierDO> selectListByStatus(Integer status) {
        return selectList(SrmSupplierDO::getStatus, status);
    }
    @Select("SELECT DISTINCT s.* FROM srm_supplier s " +
            "INNER JOIN srm_supplier_apply a ON a.supplier_id = s.id " +
            "WHERE a.apply_status = 40 AND s.status = 0 AND s.deleted = 0")
    List<SrmSupplierDO> selectApprovedList();
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierQuoteMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierQuoteDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmSupplierQuoteMapper extends BaseMapperX<SrmSupplierQuoteDO> {
    default List<SrmSupplierQuoteDO> selectListByBidId(Long bidId) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierQuoteDO>()
                .eq(SrmSupplierQuoteDO::getBidId, bidId));
    }
    default boolean existsByBidAndMaterial(Long bidId, Long tenderMaterialId) {
        return selectCount(new LambdaQueryWrapperX<SrmSupplierQuoteDO>()
                .eq(SrmSupplierQuoteDO::getBidId, bidId)
                .eq(SrmSupplierQuoteDO::getTenderMaterialId, tenderMaterialId)) > 0;
    }
    default void deleteByBidId(Long bidId) {
        delete(new LambdaQueryWrapperX<SrmSupplierQuoteDO>()
                .eq(SrmSupplierQuoteDO::getBidId, bidId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmTenderBidMapper extends BaseMapperX<SrmTenderBidDO> {
    default List<SrmTenderBidDO> selectListByTenderProjectId(Long tenderProjectId) {
        return selectList(new LambdaQueryWrapperX<SrmTenderBidDO>()
                .eq(SrmTenderBidDO::getTenderProjectId, tenderProjectId));
    }
    default SrmTenderBidDO selectByTenderAndSupplier(Long tenderProjectId, Long supplierId) {
        return selectOne(new LambdaQueryWrapperX<SrmTenderBidDO>()
                .eq(SrmTenderBidDO::getTenderProjectId, tenderProjectId)
                .eq(SrmTenderBidDO::getSupplierId, supplierId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderMaterialMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderMaterialDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmTenderMaterialMapper extends BaseMapperX<SrmTenderMaterialDO> {
    default List<SrmTenderMaterialDO> selectListByTenderProjectId(Long tenderProjectId) {
        return selectList(new LambdaQueryWrapperX<SrmTenderMaterialDO>()
                .eq(SrmTenderMaterialDO::getTenderProjectId, tenderProjectId)
                .orderByAsc(SrmTenderMaterialDO::getSort));
    }
    default void deleteByTenderProjectId(Long tenderProjectId) {
        delete(new LambdaQueryWrapperX<SrmTenderMaterialDO>()
                .eq(SrmTenderMaterialDO::getTenderProjectId, tenderProjectId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderProjectMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SrmTenderProjectMapper extends BaseMapperX<SrmTenderProjectDO> {
    default PageResult<SrmTenderProjectDO> selectPage(SrmTenderProjectPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmTenderProjectDO>()
                .likeIfPresent(SrmTenderProjectDO::getTenderNo, reqVO.getTenderNo())
                .likeIfPresent(SrmTenderProjectDO::getTenderName, reqVO.getTenderName())
                .eqIfPresent(SrmTenderProjectDO::getTenderType, reqVO.getTenderType())
                .eqIfPresent(SrmTenderProjectDO::getTenderStatus, reqVO.getTenderStatus())
                .orderByDesc(SrmTenderProjectDO::getId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/AwardStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * å®šæ ‡çŠ¶æ€æžšä¸¾
 */
@Getter
public enum AwardStatusEnum {
    PENDING_APPROVAL(0, "待审批"),
    AWARDED(10, "已定标"),
    ORDER_GENERATED(20, "已生成订单");
    AwardStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * æŠ•标状态枚举
 */
@Getter
public enum BidStatusEnum {
    REGISTERED(0, "报名"),
    BID(10, "已投标"),
    WITHDRAWN(20, "撤标"),
    WIN(30, "中标"),
    LOSE(40, "未中标");
    BidStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/CertificateTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * èµ„质证书类型枚举
 */
@Getter
public enum CertificateTypeEnum {
    BUSINESS_LICENSE(1, "营业执照"),
    CERTIFICATION(2, "认证证书"),
    INSPECTION_REPORT(3, "检测报告"),
    OTHER(4, "其他");
    CertificateTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/DeliveryStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * äº¤è´§çŠ¶æ€æžšä¸¾
 */
@Getter
public enum DeliveryStatusEnum {
    PENDING(0, "未交货"),
    PARTIAL(10, "部分交货"),
    COMPLETED(20, "已完成");
    DeliveryStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.srm.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
 * SRM é”™è¯¯ç æžšä¸¾
 * åŒºé—´ï¼š1_040_000_000 - 1_040_999_999
 */
public interface ErrorCodeConstants {
    // ========== ä¾›åº”商基础管理 1_040_100_000 ==========
    ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_040_100_000, "供应商不存在");
    ErrorCode SUPPLIER_CODE_EXISTS = new ErrorCode(1_040_100_001, "供应商编码已存在");
    ErrorCode SUPPLIER_EXISTS_REFERENCE = new ErrorCode(1_040_100_002, "供应商被业务引用,无法删除");
    ErrorCode SUPPLIER_CONTACT_NOT_EXISTS = new ErrorCode(1_040_100_003, "供应商联系人不存在");
    // ========== ä¾›åº”商准入 1_040_200_000 ==========
    ErrorCode APPLY_NOT_EXISTS = new ErrorCode(1_040_200_000, "准入申请不存在");
    ErrorCode APPLY_STATUS_NOT_DRAFT = new ErrorCode(1_040_200_001, "当前状态不允许提交");
    ErrorCode APPLY_STATUS_NOT_PENDING = new ErrorCode(1_040_200_002, "当前状态不允许审核");
    ErrorCode APPLY_SUPPLIER_EXISTS = new ErrorCode(1_040_200_003, "该供应商已存在准入记录");
    ErrorCode APPLY_SUPPLIER_NAME_REQUIRED = new ErrorCode(1_040_200_004, "未选择SRM供应商时,供应商名称不能为空");
    // ========== ä¾›åº”商资质 1_040_300_000 ==========
    ErrorCode CERTIFICATE_NOT_EXISTS = new ErrorCode(1_040_300_000, "资质证书不存在");
    ErrorCode CERTIFICATE_EXPIRED = new ErrorCode(1_040_300_001, "资质证书已过期");
    // ========== ä¾›åº”商评价 1_040_400_000 ==========
    ErrorCode EVALUATION_NOT_EXISTS = new ErrorCode(1_040_400_000, "绩效评价不存在");
    ErrorCode EVALUATION_PERIOD_EXISTS = new ErrorCode(1_040_400_001, "该评价周期已存在评价记录");
    // ========== æ‹›æŠ•标管理 1_040_500_000 ==========
    ErrorCode TENDER_NOT_EXISTS = new ErrorCode(1_040_500_000, "招标项目不存在");
    ErrorCode TENDER_STATUS_NOT_DRAFT = new ErrorCode(1_040_500_001, "当前状态不允许编辑");
    ErrorCode TENDER_STATUS_NOT_PUBLISHED = new ErrorCode(1_040_500_002, "当前状态不允许投标");
    ErrorCode TENDER_STATUS_NOT_BIDDING = new ErrorCode(1_040_500_009, "当前状态不允许评标");
    ErrorCode TENDER_STATUS_ALREADY_CLOSED = new ErrorCode(1_040_500_010, "已关闭的项目不允许操作");
    ErrorCode BID_NOT_EXISTS = new ErrorCode(1_040_500_003, "投标记录不存在");
    ErrorCode BID_SUPPLIER_EXISTS = new ErrorCode(1_040_500_004, "供应商已参与投标");
    ErrorCode BID_PAST_END_TIME = new ErrorCode(1_040_500_005, "投标已截止");
    ErrorCode BID_SUPPLIER_NOT_APPROVED = new ErrorCode(1_040_500_011, "供应商未通过准入审批,无法参与投标");
    ErrorCode BID_WITHDRAWN_CANNOT_QUOTE = new ErrorCode(1_040_500_012, "投标已撤标,不允许报价");
    ErrorCode EVALUATION_NOT_EXISTS_TENDER = new ErrorCode(1_040_500_006, "评标记录不存在");
    ErrorCode AWARD_NOT_EXISTS = new ErrorCode(1_040_500_007, "定标记录不存在");
    ErrorCode AWARD_SUPPLIER_EXISTS = new ErrorCode(1_040_500_008, "已存在定标记录");
    ErrorCode AWARD_STATUS_NOT_AWARDED = new ErrorCode(1_040_500_013, "仅已定标状态可生成采购订单");
    ErrorCode AWARD_SUPPLIER_NOT_SYNCED = new ErrorCode(1_040_500_014, "供应商未同步至ERP,无法生成采购订单");
    ErrorCode AWARD_QUOTE_NOT_EXISTS = new ErrorCode(1_040_500_015, "中标供应商报价不存在");
    // ========== é€šç”¨ ==========
    ErrorCode QUOTE_MATERIAL_DUPLICATE = new ErrorCode(1_040_900_001, "该投标已存在相同物料的报价");
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierApplyStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * ä¾›åº”商准入申请状态枚举
 */
@Getter
public enum SupplierApplyStatusEnum {
    DRAFT(0, "待提交"),
    PENDING_PURCHASE(10, "待采购审核"),
    PENDING_QUALITY(20, "待质量审核"),
    PENDING_FINANCE(30, "待财务审核"),
    APPROVED(40, "已通过"),
    REJECTED(50, "已拒绝");
    SupplierApplyStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * ä¾›åº”商类型枚举
 */
@Getter
public enum SupplierTypeEnum {
    PRODUCTION(1, "生产型"),
    TRADING(2, "贸易型"),
    SERVICE(3, "服务型"),
    OTHER(4, "其他");
    SupplierTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * æ‹›æ ‡çŠ¶æ€æžšä¸¾
 */
@Getter
public enum TenderStatusEnum {
    DRAFT(0, "草稿"),
    PUBLISHED(1, "发布中"),
    BIDDING(2, "投标中"),
    EVALUATING(3, "评标中"),
    AWARDED(4, "已定标"),
    CLOSED(5, "已关闭");
    TenderStatusEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.srm.enums;
import lombok.Getter;
/**
 * æ‹›æ ‡ç±»åž‹æžšä¸¾
 */
@Getter
public enum TenderTypeEnum {
    PUBLIC(1, "公开招标"),
    INVITATION(2, "邀请招标"),
    COMPETITIVE_NEGOTIATION(3, "竞争性谈判");
    TenderTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    private final Integer code;
    private final String name;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/framework/web/config/SrmWebConfiguration.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.srm.framework.web.config;
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * srm æ¨¡å—çš„ web ç»„ä»¶çš„ Configuration
 */
@Configuration(proxyBeanMethods = false)
public class SrmWebConfiguration {
    /**
     * srm æ¨¡å—çš„ API åˆ†ç»„
     */
    @Bean
    public GroupedOpenApi srmGroupedOpenApi() {
        return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("srm");
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.srm.service.apply;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.SrmSupplierApplyPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.SrmSupplierApplySaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierApplyDO;
import jakarta.validation.Valid;
/**
 * SRM ä¾›åº”商准入申请 Service æŽ¥å£
 */
public interface SrmSupplierApplyService {
    /**
     * åˆ›å»ºå‡†å…¥ç”³è¯·
     */
    Long createApply(@Valid SrmSupplierApplySaveReqVO createReqVO);
    /**
     * æ›´æ–°å‡†å…¥ç”³è¯·
     */
    void updateApply(@Valid SrmSupplierApplySaveReqVO updateReqVO);
    /**
     * åˆ é™¤å‡†å…¥ç”³è¯·
     */
    void deleteApply(Long id);
    /**
     * èŽ·å–å‡†å…¥ç”³è¯·
     */
    SrmSupplierApplyDO getApply(Long id);
    /**
     * åˆ†é¡µæŸ¥è¯¢å‡†å…¥ç”³è¯·
     */
    PageResult<SrmSupplierApplyDO> getApplyPage(SrmSupplierApplyPageReqVO pageReqVO);
    /**
     * æäº¤ç”³è¯·ï¼ˆå¾…采购审核)
     */
    void submitApply(Long id);
    /**
     * é‡‡è´­å®¡æ ¸
     */
    void reviewPurchase(Long id, String opinion, Boolean passed);
    /**
     * è´¨é‡å®¡æ ¸
     */
    void reviewQuality(Long id, String opinion, Boolean passed);
    /**
     * è´¢åŠ¡å®¡æ ¸
     */
    void reviewFinance(Long id, String opinion, Boolean passed);
    /**
     * é€šè¿‡åŽè‡ªåŠ¨åˆ›å»ºä¾›åº”å•†
     */
    void approve(Long id);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
package cn.iocoder.yudao.module.srm.service.apply;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.SrmSupplierApplyPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.apply.vo.SrmSupplierApplySaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierApplyDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.SupplierApplyStatusEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
/**
 * SRM ä¾›åº”商准入申请 Service å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmSupplierApplyServiceImpl implements SrmSupplierApplyService {
    @Resource
    private SrmSupplierApplyMapper supplierApplyMapper;
    @Resource
    private SrmSupplierMapper supplierMapper;
    @Override
    public Long createApply(SrmSupplierApplySaveReqVO createReqVO) {
        // æ ¡éªŒï¼šå¿…须选择 SRM ä¾›åº”商或手动填写供应商名称
        if (createReqVO.getSupplierId() != null) {
            SrmSupplierDO supplier = supplierMapper.selectById(createReqVO.getSupplierId());
            if (supplier == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
            }
        } else if (StrUtil.isBlank(createReqVO.getSupplierName())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_SUPPLIER_NAME_REQUIRED);
        }
        SrmSupplierApplyDO apply = BeanUtils.toBean(createReqVO, SrmSupplierApplyDO.class);
        apply.setApplyNo(generateApplyNo());
        apply.setApplyStatus(SupplierApplyStatusEnum.DRAFT.getCode());
        apply.setApplyTime(LocalDateTime.now());
        supplierApplyMapper.insert(apply);
        return apply.getId();
    }
    @Override
    public void updateApply(SrmSupplierApplySaveReqVO updateReqVO) {
        SrmSupplierApplyDO apply = validateApplyExists(updateReqVO.getId());
        if (!SupplierApplyStatusEnum.DRAFT.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_DRAFT);
        }
        // æ ¡éªŒï¼šå¿…须选择 SRM ä¾›åº”商或手动填写供应商名称
        if (updateReqVO.getSupplierId() != null) {
            SrmSupplierDO supplier = supplierMapper.selectById(updateReqVO.getSupplierId());
            if (supplier == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
            }
        } else if (StrUtil.isBlank(updateReqVO.getSupplierName())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_SUPPLIER_NAME_REQUIRED);
        }
        SrmSupplierApplyDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierApplyDO.class);
        supplierApplyMapper.updateById(updateObj);
    }
    @Override
    public void deleteApply(Long id) {
        validateApplyExists(id);
        supplierApplyMapper.deleteById(id);
    }
    @Override
    public SrmSupplierApplyDO getApply(Long id) {
        return supplierApplyMapper.selectById(id);
    }
    @Override
    public PageResult<SrmSupplierApplyDO> getApplyPage(SrmSupplierApplyPageReqVO pageReqVO) {
        return supplierApplyMapper.selectPage(pageReqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitApply(Long id) {
        SrmSupplierApplyDO apply = validateApplyExists(id);
        if (!SupplierApplyStatusEnum.DRAFT.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_DRAFT);
        }
        apply.setApplyStatus(SupplierApplyStatusEnum.PENDING_PURCHASE.getCode());
        apply.setApplyTime(LocalDateTime.now());
        supplierApplyMapper.updateById(apply);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reviewPurchase(Long id, String opinion, Boolean passed) {
        SrmSupplierApplyDO apply = validateApplyExists(id);
        if (!SupplierApplyStatusEnum.PENDING_PURCHASE.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_PENDING);
        }
        apply.setPurchaseReviewOpinion(opinion);
        apply.setPurchaseReviewTime(LocalDateTime.now());
        apply.setApplyStatus(Boolean.TRUE.equals(passed)
                ? SupplierApplyStatusEnum.PENDING_QUALITY.getCode()
                : SupplierApplyStatusEnum.REJECTED.getCode());
        supplierApplyMapper.updateById(apply);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reviewQuality(Long id, String opinion, Boolean passed) {
        SrmSupplierApplyDO apply = validateApplyExists(id);
        if (!SupplierApplyStatusEnum.PENDING_QUALITY.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_PENDING);
        }
        apply.setQualityReviewOpinion(opinion);
        apply.setQualityReviewTime(LocalDateTime.now());
        apply.setApplyStatus(Boolean.TRUE.equals(passed)
                ? SupplierApplyStatusEnum.PENDING_FINANCE.getCode()
                : SupplierApplyStatusEnum.REJECTED.getCode());
        supplierApplyMapper.updateById(apply);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reviewFinance(Long id, String opinion, Boolean passed) {
        SrmSupplierApplyDO apply = validateApplyExists(id);
        if (!SupplierApplyStatusEnum.PENDING_FINANCE.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_PENDING);
        }
        apply.setFinanceReviewOpinion(opinion);
        apply.setFinanceReviewTime(LocalDateTime.now());
        apply.setApplyStatus(Boolean.TRUE.equals(passed)
                ? SupplierApplyStatusEnum.APPROVED.getCode()
                : SupplierApplyStatusEnum.REJECTED.getCode());
        supplierApplyMapper.updateById(apply);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(Long id) {
        SrmSupplierApplyDO apply = validateApplyExists(id);
        if (!SupplierApplyStatusEnum.APPROVED.getCode().equals(apply.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_STATUS_NOT_PENDING);
        }
        // å¦‚果已关联 SRM ä¾›åº”商,不需要重复创建
        if (apply.getSupplierId() == null) {
            SrmSupplierDO supplier = new SrmSupplierDO();
            supplier.setName(apply.getSupplierName());
            supplier.setCompanyNature(apply.getCompanyNature());
            supplier.setRegisteredCapital(apply.getRegisteredCapital());
            supplier.setBusinessLicenseNo(apply.getBusinessLicenseNo());
            supplier.setTaxNo(apply.getTaxNo());
            supplier.setAddress(apply.getAddress());
            supplier.setContactName(apply.getContactName());
            supplier.setContactMobile(apply.getContactMobile());
            supplier.setContactEmail(apply.getContactEmail());
            supplier.setStatus(0);
            supplierMapper.insert(supplier);
            apply.setSupplierId(supplier.getId());
        }
        supplierApplyMapper.updateById(apply);
    }
    // ========== æ ¡éªŒæ–¹æ³• ==========
    private SrmSupplierApplyDO validateApplyExists(Long id) {
        SrmSupplierApplyDO apply = supplierApplyMapper.selectById(id);
        if (apply == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.APPLY_NOT_EXISTS);
        }
        return apply;
    }
    /**
     * ç”Ÿæˆç”³è¯·ç¼–号
     */
    private String generateApplyNo() {
        String prefix = "AP";
        String date = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMM"));
        int seq = (int) (Math.random() * 9000) + 1000;
        return prefix + date + String.format("%04d", seq);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.srm.service.certificate;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificatePageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificateSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCertificateDO;
import jakarta.validation.Valid;
import java.util.List;
public interface SrmSupplierCertificateService {
    Long createCertificate(@Valid SrmSupplierCertificateSaveReqVO createReqVO);
    void updateCertificate(@Valid SrmSupplierCertificateSaveReqVO updateReqVO);
    void deleteCertificate(Long id);
    SrmSupplierCertificateDO getCertificate(Long id);
    PageResult<SrmSupplierCertificateDO> getCertificatePage(SrmSupplierCertificatePageReqVO pageReqVO);
    List<SrmSupplierCertificateDO> getCertificateListBySupplierId(Long supplierId);
    List<SrmSupplierCertificateDO> getExpiringCertificates();
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.srm.service.certificate;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificatePageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificateSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCertificateDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCertificateMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.util.List;
@Service
@Validated
public class SrmSupplierCertificateServiceImpl implements SrmSupplierCertificateService {
    @Resource
    private SrmSupplierCertificateMapper certificateMapper;
    @Override
    public Long createCertificate(SrmSupplierCertificateSaveReqVO createReqVO) {
        SrmSupplierCertificateDO certificate = BeanUtils.toBean(createReqVO, SrmSupplierCertificateDO.class);
        updateStatus(certificate);
        certificateMapper.insert(certificate);
        return certificate.getId();
    }
    @Override
    public void updateCertificate(SrmSupplierCertificateSaveReqVO updateReqVO) {
        validateCertificateExists(updateReqVO.getId());
        SrmSupplierCertificateDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierCertificateDO.class);
        updateStatus(updateObj);
        certificateMapper.updateById(updateObj);
    }
    @Override
    public void deleteCertificate(Long id) {
        validateCertificateExists(id);
        certificateMapper.deleteById(id);
    }
    @Override
    public SrmSupplierCertificateDO getCertificate(Long id) {
        return certificateMapper.selectById(id);
    }
    @Override
    public PageResult<SrmSupplierCertificateDO> getCertificatePage(SrmSupplierCertificatePageReqVO pageReqVO) {
        return certificateMapper.selectPage(pageReqVO);
    }
    @Override
    public List<SrmSupplierCertificateDO> getCertificateListBySupplierId(Long supplierId) {
        return certificateMapper.selectListBySupplierId(supplierId);
    }
    @Override
    public List<SrmSupplierCertificateDO> getExpiringCertificates() {
        LocalDate now = LocalDate.now();
        return certificateMapper.selectExpiringList(now, now.plusDays(30));
    }
    private void validateCertificateExists(Long id) {
        if (certificateMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.CERTIFICATE_NOT_EXISTS);
        }
    }
    private void updateStatus(SrmSupplierCertificateDO certificate) {
        if (certificate.getValidEndDate() != null) {
            LocalDate now = LocalDate.now();
            if (certificate.getValidEndDate().isBefore(now)) {
                certificate.setStatus(1); // å·²è¿‡æœŸ
            } else if (certificate.getValidEndDate().isBefore(now.plusDays(30))) {
                certificate.setStatus(2); // å³å°†è¿‡æœŸ
            } else {
                certificate.setStatus(0); // æœ‰æ•ˆ
            }
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.srm.service.evaluation;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.SrmSupplierEvaluationPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.SrmSupplierEvaluationSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierEvaluationDO;
import jakarta.validation.Valid;
/**
 * SRM ä¾›åº”商绩效评价 Service æŽ¥å£
 */
public interface SrmSupplierEvaluationService {
    /**
     * åˆ›å»ºç»©æ•ˆè¯„ä»·
     */
    Long createEvaluation(@Valid SrmSupplierEvaluationSaveReqVO createReqVO);
    /**
     * æ›´æ–°ç»©æ•ˆè¯„ä»·
     */
    void updateEvaluation(@Valid SrmSupplierEvaluationSaveReqVO updateReqVO);
    /**
     * åˆ é™¤ç»©æ•ˆè¯„ä»·
     */
    void deleteEvaluation(Long id);
    /**
     * èŽ·å–ç»©æ•ˆè¯„ä»·
     */
    SrmSupplierEvaluationDO getEvaluation(Long id);
    /**
     * åˆ†é¡µæŸ¥è¯¢ç»©æ•ˆè¯„ä»·
     */
    PageResult<SrmSupplierEvaluationDO> getEvaluationPage(SrmSupplierEvaluationPageReqVO pageReqVO);
    /**
     * è®¡ç®—综合评分并确定等级
     * ç»¼åˆè¯„分 = è´¨é‡*0.4 + äº¤ä»˜*0.3 + ä»·æ ¼*0.2 + æœåŠ¡*0.1
     * ç­‰çº§: A>=90, B>=80, C>=70, D<70
     */
    void calculateCompositeScore(SrmSupplierEvaluationDO evaluation);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.srm.service.evaluation;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.SrmSupplierEvaluationPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.evaluation.vo.SrmSupplierEvaluationSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierEvaluationDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierEvaluationMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
 * SRM ä¾›åº”商绩效评价 Service å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmSupplierEvaluationServiceImpl implements SrmSupplierEvaluationService {
    @Resource
    private SrmSupplierEvaluationMapper evaluationMapper;
    @Override
    public Long createEvaluation(SrmSupplierEvaluationSaveReqVO createReqVO) {
        // æ ¡éªŒæ˜¯å¦å·²å­˜åœ¨ç›¸åŒå‘¨æœŸçš„评价
        SrmSupplierEvaluationDO existing = evaluationMapper.selectBySupplierAndPeriod(createReqVO.getSupplierId(), createReqVO.getEvaluationPeriod());
        if (existing != null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.EVALUATION_PERIOD_EXISTS);
        }
        SrmSupplierEvaluationDO evaluation = BeanUtils.toBean(createReqVO, SrmSupplierEvaluationDO.class);
        calculateCompositeScore(evaluation);
        evaluation.setEvaluationTime(LocalDateTime.now());
        evaluationMapper.insert(evaluation);
        return evaluation.getId();
    }
    @Override
    public void updateEvaluation(SrmSupplierEvaluationSaveReqVO updateReqVO) {
        validateEvaluationExists(updateReqVO.getId());
        SrmSupplierEvaluationDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierEvaluationDO.class);
        calculateCompositeScore(updateObj);
        evaluationMapper.updateById(updateObj);
    }
    @Override
    public void deleteEvaluation(Long id) {
        validateEvaluationExists(id);
        evaluationMapper.deleteById(id);
    }
    @Override
    public SrmSupplierEvaluationDO getEvaluation(Long id) {
        return evaluationMapper.selectById(id);
    }
    @Override
    public PageResult<SrmSupplierEvaluationDO> getEvaluationPage(SrmSupplierEvaluationPageReqVO pageReqVO) {
        return evaluationMapper.selectPage(pageReqVO);
    }
    @Override
    public void calculateCompositeScore(SrmSupplierEvaluationDO evaluation) {
        BigDecimal quality = nvl(evaluation.getQualityScore());
        BigDecimal delivery = nvl(evaluation.getDeliveryScore());
        BigDecimal price = nvl(evaluation.getPriceScore());
        BigDecimal service = nvl(evaluation.getServiceScore());
        BigDecimal composite = quality.multiply(new BigDecimal("0.4"))
                .add(delivery.multiply(new BigDecimal("0.3")))
                .add(price.multiply(new BigDecimal("0.2")))
                .add(service.multiply(new BigDecimal("0.1")))
                .setScale(2, RoundingMode.HALF_UP);
        evaluation.setCompositeScore(composite);
        if (composite.compareTo(new BigDecimal("90")) >= 0) {
            evaluation.setEvaluationLevel("A");
        } else if (composite.compareTo(new BigDecimal("80")) >= 0) {
            evaluation.setEvaluationLevel("B");
        } else if (composite.compareTo(new BigDecimal("70")) >= 0) {
            evaluation.setEvaluationLevel("C");
        } else {
            evaluation.setEvaluationLevel("D");
        }
    }
    private void validateEvaluationExists(Long id) {
        if (evaluationMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.EVALUATION_NOT_EXISTS);
        }
    }
    private static BigDecimal nvl(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.srm.service.statistics;
import cn.iocoder.yudao.module.srm.controller.admin.statistics.vo.SrmTenderStatisticsRespVO;
/**
 * SRM ç»Ÿè®¡åˆ†æž Service æŽ¥å£
 */
public interface SrmStatisticsService {
    /**
     * èŽ·å–æ‹›æ ‡ç»Ÿè®¡æ•°æ®
     *
     * @param year ç»Ÿè®¡å¹´ä»½
     */
    SrmTenderStatisticsRespVO getTenderStatistics(String year);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.srm.service.statistics;
import cn.iocoder.yudao.module.srm.controller.admin.statistics.vo.SrmTenderStatisticsRespVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SrmStatisticsServiceImpl implements SrmStatisticsService {
    @Resource
    private SrmTenderProjectMapper tenderProjectMapper;
    @Resource
    private SrmTenderBidMapper tenderBidMapper;
    @Resource
    private SrmBidAwardMapper bidAwardMapper;
    @Override
    public SrmTenderStatisticsRespVO getTenderStatistics(String year) {
        // æŸ¥è¯¢å½“年所有招标项目
        List<SrmTenderProjectDO> projects = tenderProjectMapper.selectList(
                new LambdaQueryWrapperX<SrmTenderProjectDO>()
                        .apply("YEAR(create_time) = {0}", year));
        // æŸ¥è¯¢å½“年所有定标记录
        List<SrmBidAwardDO> awards = bidAwardMapper.selectList(
                new LambdaQueryWrapperX<SrmBidAwardDO>()
                        .apply("YEAR(create_time) = {0}", year));
        // æŸ¥è¯¢å½“年所有投标供应商数(去重)
        List<SrmTenderBidDO> bids = tenderBidMapper.selectList(
                new LambdaQueryWrapperX<SrmTenderBidDO>()
                        .apply("YEAR(create_time) = {0}", year));
        long totalTenderCount = projects.size();
        BigDecimal totalTenderAmount = projects.stream()
                .map(p -> p.getBudgetAmount() != null ? p.getBudgetAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalAwardAmount = awards.stream()
                .map(a -> a.getAwardAmount() != null ? a.getAwardAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal savedAmount = totalTenderAmount.subtract(totalAwardAmount);
        long supplierCount = bids.stream()
                .map(SrmTenderBidDO::getSupplierId)
                .distinct()
                .count();
        BigDecimal winRate = BigDecimal.ZERO;
        if (totalTenderCount > 0) {
            long awardedProjectCount = awards.stream()
                    .map(SrmBidAwardDO::getTenderProjectId)
                    .distinct()
                    .count();
            winRate = BigDecimal.valueOf(awardedProjectCount)
                    .multiply(BigDecimal.valueOf(100))
                    .divide(BigDecimal.valueOf(totalTenderCount), 1, RoundingMode.HALF_UP);
        }
        SrmTenderStatisticsRespVO vo = new SrmTenderStatisticsRespVO();
        vo.setTotalTenderCount(totalTenderCount);
        vo.setTotalTenderAmount(totalTenderAmount);
        vo.setTotalAwardAmount(totalAwardAmount);
        vo.setSavedAmount(savedAmount);
        vo.setWinRate(winRate);
        vo.setSupplierCount(supplierCount);
        vo.setPeriod(year);
        return vo;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierCategoryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCategoryDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * SRM ä¾›åº”商分类关联 Service æŽ¥å£
 */
public interface SrmSupplierCategoryService {
    void assignCategory(@Valid Long supplierId, Long categoryId, String categoryName);
    void removeCategory(Long id);
    List<SrmSupplierCategoryDO> getCategoryListBySupplierId(Long supplierId);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierCategoryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierCategoryDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCategoryMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SrmSupplierCategoryServiceImpl implements SrmSupplierCategoryService {
    @Resource
    private SrmSupplierCategoryMapper supplierCategoryMapper;
    @Override
    public void assignCategory(Long supplierId, Long categoryId, String categoryName) {
        SrmSupplierCategoryDO category = SrmSupplierCategoryDO.builder()
                .supplierId(supplierId)
                .categoryId(categoryId)
                .categoryName(categoryName)
                .build();
        supplierCategoryMapper.insert(category);
    }
    @Override
    public void removeCategory(Long id) {
        supplierCategoryMapper.deleteById(id);
    }
    @Override
    public List<SrmSupplierCategoryDO> getCategoryListBySupplierId(Long supplierId) {
        return supplierCategoryMapper.selectListBySupplierId(supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierContactService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierContactSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * ä¾›åº”商联系人 Service æŽ¥å£
 */
public interface SrmSupplierContactService {
    /**
     * åˆ›å»ºè”系人
     */
    Long createContact(@Valid SrmSupplierContactSaveReqVO createReqVO);
    /**
     * æ›´æ–°è”系人
     */
    void updateContact(@Valid SrmSupplierContactSaveReqVO updateReqVO);
    /**
     * åˆ é™¤è”系人
     */
    void deleteContact(Long id);
    /**
     * èŽ·å–ä¾›åº”å•†çš„è”ç³»äººåˆ—è¡¨
     */
    List<SrmSupplierContactDO> getContactListBySupplierId(Long supplierId);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierContactServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierContactSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierContactMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@Service
@Validated
public class SrmSupplierContactServiceImpl implements SrmSupplierContactService {
    @Resource
    private SrmSupplierContactMapper supplierContactMapper;
    @Override
    public Long createContact(SrmSupplierContactSaveReqVO createReqVO) {
        SrmSupplierContactDO contact = BeanUtils.toBean(createReqVO, SrmSupplierContactDO.class);
        supplierContactMapper.insert(contact);
        return contact.getId();
    }
    @Override
    public void updateContact(SrmSupplierContactSaveReqVO updateReqVO) {
        validateContactExists(updateReqVO.getId());
        SrmSupplierContactDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierContactDO.class);
        supplierContactMapper.updateById(updateObj);
    }
    @Override
    public void deleteContact(Long id) {
        validateContactExists(id);
        supplierContactMapper.deleteById(id);
    }
    @Override
    public List<SrmSupplierContactDO> getContactListBySupplierId(Long supplierId) {
        return supplierContactMapper.selectListBySupplierId(supplierId);
    }
    private void validateContactExists(Long id) {
        if (supplierContactMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_CONTACT_NOT_EXISTS);
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * ä¾›åº”商 Service æŽ¥å£
 */
public interface SrmSupplierService {
    /**
     * åˆ›å»ºä¾›åº”商
     */
    Long createSupplier(@Valid SrmSupplierSaveReqVO createReqVO);
    /**
     * æ›´æ–°ä¾›åº”商
     */
    void updateSupplier(@Valid SrmSupplierSaveReqVO updateReqVO);
    /**
     * åˆ é™¤ä¾›åº”商
     */
    void deleteSupplier(Long id);
    /**
     * èŽ·å–ä¾›åº”å•†
     */
    SrmSupplierDO getSupplier(Long id);
    /**
     * åˆ†é¡µæŸ¥è¯¢ä¾›åº”商
     */
    PageResult<SrmSupplierDO> getSupplierPage(SrmSupplierPageReqVO pageReqVO);
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨ï¼ˆä»…è¿”å›žå·²é€šè¿‡å‡†å…¥å®¡æ‰¹çš„ä¾›åº”å•†ï¼Œç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
     */
    List<SrmSupplierDO> getSupplierSimpleList();
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨
     */
    List<SrmSupplierDO> getSupplierList(Collection<Long> ids);
    /**
     * èŽ·å–ä¾›åº”å•† Map
     */
    default Map<Long, SrmSupplierDO> getSupplierMap(Collection<Long> ids) {
        List<SrmSupplierDO> list = getSupplierList(ids);
        return list.stream().collect(java.util.stream.Collectors.toMap(SrmSupplierDO::getId, s -> s));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCategoryMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierContactMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
@Service
@Validated
public class SrmSupplierServiceImpl implements SrmSupplierService {
    @Resource
    private SrmSupplierMapper supplierMapper;
    @Resource
    private SrmSupplierContactMapper supplierContactMapper;
    @Resource
    private SrmSupplierCategoryMapper supplierCategoryMapper;
    @Override
    public Long createSupplier(SrmSupplierSaveReqVO createReqVO) {
        // æ ¡éªŒä¾›åº”商编码唯一性
        validateCodeUnique(createReqVO.getCode(), null);
        // æ’å…¥
        SrmSupplierDO supplier = BeanUtils.toBean(createReqVO, SrmSupplierDO.class);
        supplierMapper.insert(supplier);
        return supplier.getId();
    }
    @Override
    public void updateSupplier(SrmSupplierSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨æ€§
        validateSupplierExists(updateReqVO.getId());
        // æ ¡éªŒç¼–码唯一性
        validateCodeUnique(updateReqVO.getCode(), updateReqVO.getId());
        // æ›´æ–°
        SrmSupplierDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierDO.class);
        supplierMapper.updateById(updateObj);
    }
    @Override
    public void deleteSupplier(Long id) {
        // æ ¡éªŒå­˜åœ¨æ€§
        validateSupplierExists(id);
        // åˆ é™¤å…³è”数据
        supplierContactMapper.deleteBySupplierId(id);
        supplierCategoryMapper.deleteBySupplierId(id);
        // é€»è¾‘删除
        supplierMapper.deleteById(id);
    }
    @Override
    public SrmSupplierDO getSupplier(Long id) {
        return supplierMapper.selectById(id);
    }
    @Override
    public PageResult<SrmSupplierDO> getSupplierPage(SrmSupplierPageReqVO pageReqVO) {
        return supplierMapper.selectPage(pageReqVO);
    }
    @Override
    public List<SrmSupplierDO> getSupplierSimpleList() {
        return supplierMapper.selectApprovedList();
    }
    @Override
    public List<SrmSupplierDO> getSupplierList(Collection<Long> ids) {
        return supplierMapper.selectBatchIds(ids);
    }
    // ========== æ ¡éªŒæ–¹æ³• ==========
    private void validateSupplierExists(Long id) {
        if (supplierMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
        }
    }
    private void validateCodeUnique(String code, Long id) {
        SrmSupplierDO existing = supplierMapper.selectByCode(code);
        if (existing != null && !existing.getId().equals(id)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_CODE_EXISTS);
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmBidAwardSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
import jakarta.validation.Valid;
/**
 * SRM å®šæ ‡ç®¡ç† Service æŽ¥å£
 */
public interface SrmBidAwardService {
    /**
     * åˆ›å»ºå®šæ ‡
     */
    Long createAward(@Valid SrmBidAwardSaveReqVO createReqVO);
    /**
     * èŽ·å–å®šæ ‡è®°å½•
     */
    SrmBidAwardDO getAward(Long id);
    /**
     * å®¡æ‰¹å®šæ ‡
     */
    void approveAward(Long id);
    /**
     * ç”Ÿæˆé‡‡è´­è®¢å•(预留接口)
     */
    void generatePurchaseOrder(Long id);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,186 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmBidAwardSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.*;
import cn.iocoder.yudao.module.srm.dal.mysql.*;
import cn.iocoder.yudao.module.srm.enums.AwardStatusEnum;
import cn.iocoder.yudao.module.srm.enums.BidStatusEnum;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * SRM å®šæ ‡ç®¡ç† Service å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmBidAwardServiceImpl implements SrmBidAwardService {
    @Resource
    private SrmBidAwardMapper bidAwardMapper;
    @Resource
    private SrmTenderBidMapper tenderBidMapper;
    @Resource
    private SrmTenderProjectMapper tenderProjectMapper;
    @Resource
    private SrmSupplierMapper supplierMapper;
    @Resource
    private SrmTenderMaterialMapper tenderMaterialMapper;
    @Resource
    private SrmSupplierQuoteMapper supplierQuoteMapper;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private ErpPurchaseOrderApi erpPurchaseOrderApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createAward(SrmBidAwardSaveReqVO createReqVO) {
        // æ ¡éªŒæ˜¯å¦å·²å­˜åœ¨å®šæ ‡
        SrmBidAwardDO existing = bidAwardMapper.selectByTenderProjectId(createReqVO.getTenderProjectId());
        if (existing != null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_EXISTS);
        }
        SrmBidAwardDO award = BeanUtils.toBean(createReqVO, SrmBidAwardDO.class);
        award.setAwardStatus(AwardStatusEnum.PENDING_APPROVAL.getCode());
        award.setAwardTime(LocalDateTime.now());
        bidAwardMapper.insert(award);
        // æ›´æ–°æŠ•标状态为中标
        SrmTenderBidDO bid = tenderBidMapper.selectById(createReqVO.getBidId());
        if (bid != null) {
            bid.setBidStatus(BidStatusEnum.WIN.getCode());
            tenderBidMapper.updateById(bid);
        }
        return award.getId();
    }
    @Override
    public SrmBidAwardDO getAward(Long id) {
        return bidAwardMapper.selectById(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveAward(Long id) {
        SrmBidAwardDO award = validateAwardExists(id);
        award.setAwardStatus(AwardStatusEnum.AWARDED.getCode());
        award.setApprovalTime(LocalDateTime.now());
        bidAwardMapper.updateById(award);
        // æ›´æ–°æ‹›æ ‡é¡¹ç›®çŠ¶æ€ä¸ºå·²å®šæ ‡
        SrmTenderProjectDO project = tenderProjectMapper.selectById(award.getTenderProjectId());
        if (project != null) {
            project.setTenderStatus(TenderStatusEnum.AWARDED.getCode());
            tenderProjectMapper.updateById(project);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generatePurchaseOrder(Long id) {
        // 1. æ ¡éªŒå®šæ ‡å­˜åœ¨ä¸”状态为已定标
        SrmBidAwardDO award = validateAwardExists(id);
        if (!AwardStatusEnum.AWARDED.getCode().equals(award.getAwardStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_STATUS_NOT_AWARDED);
        }
        // 2. æŸ¥ SRM ä¾›åº”商
        SrmSupplierDO srmSupplier = supplierMapper.selectById(award.getSupplierId());
        if (srmSupplier == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_SYNCED);
        }
        // 3. æŸ¥æ‹›æ ‡ç‰©æ–™åˆ—表
        List<SrmTenderMaterialDO> materials = tenderMaterialMapper
                .selectListByTenderProjectId(award.getTenderProjectId());
        if (CollUtil.isEmpty(materials)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS);
        }
        // 4. æŸ¥ä¸­æ ‡æŠ¥ä»·åˆ—表
        List<SrmSupplierQuoteDO> quotes = supplierQuoteMapper.selectListByBidId(award.getBidId());
        if (CollUtil.isEmpty(quotes)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_QUOTE_NOT_EXISTS);
        }
        Map<Long, SrmSupplierQuoteDO> quoteMap = quotes.stream()
                .collect(Collectors.toMap(
                        SrmSupplierQuoteDO::getTenderMaterialId,
                        Function.identity(),
                        (a, b) -> a));
        // 5. æ‰¹é‡æŸ¥ MDM ç‰©æ–™ï¼ŒèŽ·å–è®¡é‡å•ä½
        Set<Long> productIds = materials.stream()
                .map(SrmTenderMaterialDO::getProductId)
                .collect(Collectors.toSet());
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemList(productIds).getCheckedData()
                .stream().collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity()));
        // 6. æž„建订单项
        List<ErpPurchaseOrderCreateReqDTO.Item> orderItems = new ArrayList<>();
        for (SrmTenderMaterialDO material : materials) {
            SrmSupplierQuoteDO quote = quoteMap.get(material.getId());
            if (quote == null) {
                continue;
            }
            MdmItemRespDTO mdmItem = itemMap.get(material.getProductId());
            if (mdmItem == null) {
                continue;
            }
            ErpPurchaseOrderCreateReqDTO.Item item = new ErpPurchaseOrderCreateReqDTO.Item();
            item.setProductId(material.getProductId());
            item.setProductUnitId(mdmItem.getUnitMeasureId());
            item.setProductPrice(quote.getQuotePrice());
            item.setCount(material.getQuantity());
            item.setTaxPercent(quote.getTaxRate());
            item.setRemark("招标编号: " + award.getAwardNo());
            orderItems.add(item);
        }
        // 7. æž„建并调用 ERP åˆ›å»ºé‡‡è´­è®¢å•
        ErpPurchaseOrderCreateReqDTO createReq = new ErpPurchaseOrderCreateReqDTO();
        createReq.setSupplierId(srmSupplier.getId());
        createReq.setOrderTime(LocalDateTime.now());
        createReq.setItems(orderItems);
        createReq.setRemark("SRM å®šæ ‡è‡ªåŠ¨ç”Ÿæˆ, å®šæ ‡ç¼–号: " + award.getAwardNo());
        Long purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
        // 8. æ›´æ–°å®šæ ‡è®°å½•
        award.setPurchaseOrderId(purchaseOrderId);
        award.setAwardStatus(AwardStatusEnum.ORDER_GENERATED.getCode());
        bidAwardMapper.updateById(award);
    }
    private SrmBidAwardDO validateAwardExists(Long id) {
        SrmBidAwardDO award = bidAwardMapper.selectById(id);
        if (award == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_NOT_EXISTS);
        }
        return award;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmBidEvaluationSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidEvaluationDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidOpenDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * SRM è¯„标管理 Service æŽ¥å£
 */
public interface SrmBidEvaluationService {
    // ========== è¯„标管理 ==========
    /**
     * åˆ›å»ºè¯„标记录
     */
    Long createEvaluation(@Valid SrmBidEvaluationSaveReqVO createReqVO);
    /**
     * èŽ·å–è¯„æ ‡åˆ—è¡¨ï¼ˆæŒ‰æ‹›æ ‡é¡¹ç›®ï¼‰
     */
    List<SrmBidEvaluationDO> getEvaluationList(Long tenderProjectId);
    /**
     * è‡ªåŠ¨æŽ’åè®¡ç®—
     */
    void calculateRanking(Long tenderProjectId);
    // ========== å¼€æ ‡ç®¡ç† ==========
    /**
     * åˆ›å»ºå¼€æ ‡è®°å½•
     */
    Long createBidOpen(Long tenderProjectId);
    /**
     * èŽ·å–å¼€æ ‡è®°å½•
     */
    SrmBidOpenDO getBidOpen(Long id);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmBidEvaluationSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidEvaluationDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidOpenDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidEvaluationMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidOpenMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
/**
 * SRM è¯„标管理 Service å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmBidEvaluationServiceImpl implements SrmBidEvaluationService {
    @Resource
    private SrmBidEvaluationMapper bidEvaluationMapper;
    @Resource
    private SrmBidOpenMapper bidOpenMapper;
    @Resource
    private SrmTenderProjectMapper tenderProjectMapper;
    // ========== è¯„标管理 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createEvaluation(SrmBidEvaluationSaveReqVO createReqVO) {
        SrmBidEvaluationDO evaluation = BeanUtils.toBean(createReqVO, SrmBidEvaluationDO.class);
        // è®¡ç®—综合评分: ä»·æ ¼*0.4 + æŠ€æœ¯*0.3 + äº¤ä»˜*0.2 + æœåŠ¡*0.1
        BigDecimal price = nvl(evaluation.getPriceScore());
        BigDecimal tech = nvl(evaluation.getTechScore());
        BigDecimal delivery = nvl(evaluation.getDeliveryScore());
        BigDecimal service = nvl(evaluation.getServiceScore());
        BigDecimal composite = price.multiply(new BigDecimal("0.4"))
                .add(tech.multiply(new BigDecimal("0.3")))
                .add(delivery.multiply(new BigDecimal("0.2")))
                .add(service.multiply(new BigDecimal("0.1")))
                .setScale(2, RoundingMode.HALF_UP);
        evaluation.setCompositeScore(composite);
        evaluation.setEvaluationTime(LocalDateTime.now());
        bidEvaluationMapper.insert(evaluation);
        // é¦–次评标时更新招标项目状态为评标中
        SrmTenderProjectDO project = tenderProjectMapper.selectById(createReqVO.getTenderProjectId());
        if (project != null && TenderStatusEnum.BIDDING.getCode().equals(project.getTenderStatus())) {
            project.setTenderStatus(TenderStatusEnum.EVALUATING.getCode());
            tenderProjectMapper.updateById(project);
        }
        return evaluation.getId();
    }
    @Override
    public List<SrmBidEvaluationDO> getEvaluationList(Long tenderProjectId) {
        return bidEvaluationMapper.selectListByTenderProjectId(tenderProjectId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateRanking(Long tenderProjectId) {
        List<SrmBidEvaluationDO> list = bidEvaluationMapper.selectListByTenderProjectId(tenderProjectId);
        // æŒ‰ç»¼åˆè¯„分降序排名
        list.sort(Comparator.comparing(e -> e.getCompositeScore() != null ? e.getCompositeScore() : BigDecimal.ZERO, Comparator.reverseOrder()));
        for (int i = 0; i < list.size(); i++) {
            SrmBidEvaluationDO evaluation = list.get(i);
            evaluation.setRanking(i + 1);
            bidEvaluationMapper.updateById(evaluation);
        }
    }
    // ========== å¼€æ ‡ç®¡ç† ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBidOpen(Long tenderProjectId) {
        if (bidOpenMapper.selectByTenderProjectId(tenderProjectId) != null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_PUBLISHED);
        }
        // å¼€æ ‡åŽè‡ªåŠ¨æ›´æ–°æ‹›æ ‡é¡¹ç›®çŠ¶æ€ä¸ºæŠ•æ ‡ä¸­
        SrmTenderProjectDO project = tenderProjectMapper.selectById(tenderProjectId);
        if (project != null && TenderStatusEnum.PUBLISHED.getCode().equals(project.getTenderStatus())) {
            project.setTenderStatus(TenderStatusEnum.BIDDING.getCode());
            tenderProjectMapper.updateById(project);
        }
        SrmBidOpenDO open = new SrmBidOpenDO();
        open.setTenderProjectId(tenderProjectId);
        open.setOpenTime(LocalDateTime.now());
        bidOpenMapper.insert(open);
        return open.getId();
    }
    @Override
    public SrmBidOpenDO getBidOpen(Long id) {
        return bidOpenMapper.selectById(id);
    }
    private static BigDecimal nvl(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmSupplierQuoteSaveReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderBidSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierQuoteDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * SRM æŠ•标管理 Service æŽ¥å£
 */
public interface SrmTenderBidService {
    // ========== æŠ•标管理 ==========
    /**
     * åˆ›å»ºæŠ•æ ‡
     */
    Long createBid(@Valid SrmTenderBidSaveReqVO createReqVO);
    /**
     * èŽ·å–æŠ•æ ‡è¯¦æƒ…
     */
    SrmTenderBidDO getBid(Long id);
    /**
     * æŸ¥è¯¢æ‹›æ ‡é¡¹ç›®çš„æŠ•标列表
     */
    List<SrmTenderBidDO> getBidListByProject(Long tenderProjectId);
    /**
     * æ’¤æ ‡
     */
    void withdrawBid(Long id);
    // ========== æŠ¥ä»·ç®¡ç† ==========
    /**
     * åˆ›å»ºæŠ¥ä»·
     */
    Long createQuote(@Valid SrmSupplierQuoteSaveReqVO createReqVO);
    /**
     * æ›´æ–°æŠ¥ä»·
     */
    void updateQuote(@Valid SrmSupplierQuoteSaveReqVO updateReqVO);
    /**
     * èŽ·å–æŠ¥ä»·åˆ—è¡¨ï¼ˆæŒ‰æŠ•æ ‡ID)
     */
    List<SrmSupplierQuoteDO> getQuoteList(Long bidId);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmSupplierQuoteSaveReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderBidSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierQuoteDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierQuoteMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
import cn.iocoder.yudao.module.srm.enums.BidStatusEnum;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.List;
/**
 * SRM æŠ•标管理 Service å®žçŽ°ç±»
 */
@Service
@Validated
public class SrmTenderBidServiceImpl implements SrmTenderBidService {
    @Resource
    private SrmTenderBidMapper tenderBidMapper;
    @Resource
    private SrmSupplierQuoteMapper supplierQuoteMapper;
    @Resource
    private SrmTenderProjectMapper tenderProjectMapper;
    @Resource
    private SrmSupplierApplyMapper supplierApplyMapper;
    // ========== æŠ•标管理 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBid(SrmTenderBidSaveReqVO createReqVO) {
        // æ ¡éªŒæ‹›æ ‡é¡¹ç›®æ˜¯å¦å­˜åœ¨ä¸”状态正确
        validateTenderStatus(createReqVO.getTenderProjectId());
        // æ ¡éªŒä¾›åº”商是否已通过准入审批
        if (!supplierApplyMapper.existsApprovedBySupplierId(createReqVO.getSupplierId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_NOT_APPROVED);
        }
        // æ ¡éªŒä¾›åº”商是否已参与投标
        SrmTenderBidDO existing = tenderBidMapper.selectByTenderAndSupplier(createReqVO.getTenderProjectId(), createReqVO.getSupplierId());
        if (existing != null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_EXISTS);
        }
        SrmTenderBidDO bid = BeanUtils.toBean(createReqVO, SrmTenderBidDO.class);
        bid.setBidStatus(BidStatusEnum.REGISTERED.getCode());
        bid.setBidTime(LocalDateTime.now());
        tenderBidMapper.insert(bid);
        return bid.getId();
    }
    @Override
    public SrmTenderBidDO getBid(Long id) {
        return tenderBidMapper.selectById(id);
    }
    @Override
    public List<SrmTenderBidDO> getBidListByProject(Long tenderProjectId) {
        return tenderBidMapper.selectListByTenderProjectId(tenderProjectId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawBid(Long id) {
        SrmTenderBidDO bid = validateBidExists(id);
        bid.setBidStatus(BidStatusEnum.WITHDRAWN.getCode());
        tenderBidMapper.updateById(bid);
        // åˆ é™¤æŠ¥ä»·
        supplierQuoteMapper.deleteByBidId(id);
    }
    // ========== æŠ¥ä»·ç®¡ç† ==========
    @Override
    public Long createQuote(SrmSupplierQuoteSaveReqVO createReqVO) {
        // æ ¡éªŒæŠ•标状态(撤标后不允许报价)
        validateBidNotWithdrawn(createReqVO.getBidId());
        // æ ¡éªŒåŒä¸€æŠ•标下物料不可重复报价
        if (supplierQuoteMapper.existsByBidAndMaterial(createReqVO.getBidId(), createReqVO.getTenderMaterialId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE);
        }
        SrmSupplierQuoteDO quote = BeanUtils.toBean(createReqVO, SrmSupplierQuoteDO.class);
        supplierQuoteMapper.insert(quote);
        return quote.getId();
    }
    @Override
    public void updateQuote(SrmSupplierQuoteSaveReqVO updateReqVO) {
        // æ ¡éªŒæŠ•标状态(撤标后不允许报价)
        validateBidNotWithdrawn(updateReqVO.getBidId());
        // æ ¡éªŒåŒä¸€æŠ•标下物料不可重复报价(排除自身)
        SrmSupplierQuoteDO existing = supplierQuoteMapper.selectById(updateReqVO.getId());
        if (existing != null && !existing.getTenderMaterialId().equals(updateReqVO.getTenderMaterialId())) {
            if (supplierQuoteMapper.existsByBidAndMaterial(updateReqVO.getBidId(), updateReqVO.getTenderMaterialId())) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE);
            }
        }
        SrmSupplierQuoteDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierQuoteDO.class);
        supplierQuoteMapper.updateById(updateObj);
    }
    @Override
    public List<SrmSupplierQuoteDO> getQuoteList(Long bidId) {
        return supplierQuoteMapper.selectListByBidId(bidId);
    }
    // ========== æ ¡éªŒæ–¹æ³• ==========
    private SrmTenderBidDO validateBidExists(Long id) {
        SrmTenderBidDO bid = tenderBidMapper.selectById(id);
        if (bid == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NOT_EXISTS);
        }
        return bid;
    }
    private void validateBidNotWithdrawn(Long bidId) {
        SrmTenderBidDO bid = validateBidExists(bidId);
        if (BidStatusEnum.WITHDRAWN.getCode().equals(bid.getBidStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_WITHDRAWN_CANNOT_QUOTE);
        }
    }
    private void validateTenderStatus(Long tenderProjectId) {
        if (tenderProjectMapper.selectById(tenderProjectId) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS);
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectSaveReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderMaterialSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderMaterialDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
import jakarta.validation.Valid;
import java.util.List;
public interface SrmTenderProjectService {
    Long createTenderProject(@Valid SrmTenderProjectSaveReqVO createReqVO);
    void updateTenderProject(@Valid SrmTenderProjectSaveReqVO updateReqVO);
    void deleteTenderProject(Long id);
    SrmTenderProjectDO getTenderProject(Long id);
    PageResult<SrmTenderProjectDO> getTenderProjectPage(SrmTenderProjectPageReqVO pageReqVO);
    void publishTender(Long id);
    void closeTender(Long id);
    void startBidding(Long id);
    void startEvaluation(Long id);
    // ç‰©æ–™å­è¡¨
    void createMaterial(@Valid SrmTenderMaterialSaveReqVO createReqVO);
    void updateMaterial(@Valid SrmTenderMaterialSaveReqVO updateReqVO);
    void deleteMaterial(Long id);
    List<SrmTenderMaterialDO> getMaterialList(Long tenderProjectId);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
package cn.iocoder.yudao.module.srm.service.tender;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderMaterialSaveReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderMaterialDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderMaterialMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Validated
public class SrmTenderProjectServiceImpl implements SrmTenderProjectService {
    @Resource
    private SrmTenderProjectMapper tenderProjectMapper;
    @Resource
    private SrmTenderMaterialMapper tenderMaterialMapper;
    @Resource
    private SrmBidAwardMapper bidAwardMapper;
    @Override
    public Long createTenderProject(SrmTenderProjectSaveReqVO createReqVO) {
        SrmTenderProjectDO project = BeanUtils.toBean(createReqVO, SrmTenderProjectDO.class);
        project.setTenderStatus(TenderStatusEnum.DRAFT.getCode());
        tenderProjectMapper.insert(project);
        return project.getId();
    }
    @Override
    public void updateTenderProject(SrmTenderProjectSaveReqVO updateReqVO) {
        SrmTenderProjectDO project = validateTenderExists(updateReqVO.getId());
        if (!TenderStatusEnum.DRAFT.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_DRAFT);
        }
        SrmTenderProjectDO updateObj = BeanUtils.toBean(updateReqVO, SrmTenderProjectDO.class);
        tenderProjectMapper.updateById(updateObj);
    }
    @Override
    public void deleteTenderProject(Long id) {
        SrmTenderProjectDO project = validateTenderExists(id);
        if (!TenderStatusEnum.DRAFT.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_DRAFT);
        }
        tenderMaterialMapper.deleteByTenderProjectId(id);
        tenderProjectMapper.deleteById(id);
    }
    @Override
    public SrmTenderProjectDO getTenderProject(Long id) {
        return tenderProjectMapper.selectById(id);
    }
    @Override
    public PageResult<SrmTenderProjectDO> getTenderProjectPage(SrmTenderProjectPageReqVO pageReqVO) {
        PageResult<SrmTenderProjectDO> pageResult = tenderProjectMapper.selectPage(pageReqVO);
        // æ‰¹é‡æŸ¥è¯¢å®šæ ‡ä¿¡æ¯ï¼Œå¡«å…… awardId å’Œ purchaseOrderNo
        List<Long> projectIds = pageResult.getList().stream()
                .map(SrmTenderProjectDO::getId).collect(Collectors.toList());
        if (!projectIds.isEmpty()) {
            List<SrmBidAwardDO> awards = bidAwardMapper.selectListByTenderProjectIds(projectIds);
            Map<Long, SrmBidAwardDO> awardMap = awards.stream().collect(Collectors.toMap(
                    SrmBidAwardDO::getTenderProjectId, Function.identity(), (a, b) -> a));
            pageResult.getList().forEach(project -> {
                SrmBidAwardDO award = awardMap.get(project.getId());
                if (award != null) {
                    project.setAwardId(award.getId());
                    project.setPurchaseOrderNo(award.getPurchaseOrderNo());
                }
            });
        }
        return pageResult;
    }
    @Override
    public void publishTender(Long id) {
        SrmTenderProjectDO project = validateTenderExists(id);
        if (!TenderStatusEnum.DRAFT.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_DRAFT);
        }
        project.setTenderStatus(TenderStatusEnum.PUBLISHED.getCode());
        tenderProjectMapper.updateById(project);
    }
    @Override
    public void closeTender(Long id) {
        SrmTenderProjectDO project = validateTenderExists(id);
        if (TenderStatusEnum.CLOSED.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_ALREADY_CLOSED);
        }
        project.setTenderStatus(TenderStatusEnum.CLOSED.getCode());
        tenderProjectMapper.updateById(project);
    }
    @Override
    public void startBidding(Long id) {
        SrmTenderProjectDO project = validateTenderExists(id);
        if (!TenderStatusEnum.PUBLISHED.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_PUBLISHED);
        }
        project.setTenderStatus(TenderStatusEnum.BIDDING.getCode());
        tenderProjectMapper.updateById(project);
    }
    @Override
    public void startEvaluation(Long id) {
        SrmTenderProjectDO project = validateTenderExists(id);
        if (!TenderStatusEnum.BIDDING.getCode().equals(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_BIDDING);
        }
        project.setTenderStatus(TenderStatusEnum.EVALUATING.getCode());
        tenderProjectMapper.updateById(project);
    }
    // ========== ç‰©æ–™å­è¡¨ ==========
    @Override
    public void createMaterial(SrmTenderMaterialSaveReqVO createReqVO) {
        SrmTenderMaterialDO material = BeanUtils.toBean(createReqVO, SrmTenderMaterialDO.class);
        tenderMaterialMapper.insert(material);
    }
    @Override
    public void updateMaterial(SrmTenderMaterialSaveReqVO updateReqVO) {
        SrmTenderMaterialDO updateObj = BeanUtils.toBean(updateReqVO, SrmTenderMaterialDO.class);
        tenderMaterialMapper.updateById(updateObj);
    }
    @Override
    public void deleteMaterial(Long id) {
        tenderMaterialMapper.deleteById(id);
    }
    @Override
    public List<SrmTenderMaterialDO> getMaterialList(Long tenderProjectId) {
        return tenderMaterialMapper.selectListByTenderProjectId(tenderProjectId);
    }
    private SrmTenderProjectDO validateTenderExists(Long id) {
        SrmTenderProjectDO project = tenderProjectMapper.selectById(id);
        if (project == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS);
        }
        return project;
    }
}
yudao-module-srm/src/main/resources/sql/srm_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,343 @@
-- =============================================
-- SRM ä¾›åº”商关系管理模块 - æ•°æ®åº“初始化脚本
-- MySQL 8.4+
-- =============================================
-- 1. ä¾›åº”商主表
CREATE TABLE IF NOT EXISTS srm_supplier (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '供应商ID',
    code VARCHAR(64) NOT NULL COMMENT '供应商编码',
    name VARCHAR(255) NOT NULL COMMENT '供应商名称',
    short_name VARCHAR(100) DEFAULT NULL COMMENT '供应商简称',
    type VARCHAR(50) DEFAULT NULL COMMENT '供应商类型(生产型/贸易型/服务型)',
    company_nature VARCHAR(50) DEFAULT NULL COMMENT '企业性质(国企/民营/外资/合资)',
    registered_capital DECIMAL(24,6) DEFAULT NULL COMMENT '注册资金(万元)',
    business_license_no VARCHAR(100) DEFAULT NULL COMMENT '营业执照编号',
    tax_no VARCHAR(100) DEFAULT NULL COMMENT '税号',
    legal_person VARCHAR(50) DEFAULT NULL COMMENT '法人代表',
    address VARCHAR(500) DEFAULT NULL COMMENT '公司地址',
    website VARCHAR(200) DEFAULT NULL COMMENT '公司网址',
    contact_name VARCHAR(50) DEFAULT NULL COMMENT '联系人',
    contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
    contact_email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
    bank_name VARCHAR(255) DEFAULT NULL COMMENT '开户银行',
    bank_account VARCHAR(100) DEFAULT NULL COMMENT '银行账号',
    status INT NOT NULL DEFAULT 0 COMMENT '状态(0启用 1禁用)',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    erp_supplier_id BIGINT DEFAULT NULL COMMENT '关联ERP供应商ID',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_code (code),
    KEY idx_name (name),
    KEY idx_status (status),
    KEY idx_erp_supplier_id (erp_supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商主表';
-- 2. ä¾›åº”商联系人表
CREATE TABLE IF NOT EXISTS srm_supplier_contact (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '联系人ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    name VARCHAR(50) NOT NULL COMMENT '联系人姓名',
    position VARCHAR(50) DEFAULT NULL COMMENT '职位',
    mobile VARCHAR(20) DEFAULT NULL COMMENT '手机号码',
    telephone VARCHAR(20) DEFAULT NULL COMMENT '固定电话',
    email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
    wechat VARCHAR(50) DEFAULT NULL COMMENT '微信号',
    is_default BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否默认联系人',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商联系人表';
-- 3. ä¾›åº”商分类关联表
CREATE TABLE IF NOT EXISTS srm_supplier_category (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '关联ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    category_id BIGINT NOT NULL COMMENT '分类ID',
    category_name VARCHAR(100) DEFAULT NULL COMMENT '分类名称(冗余)',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_category_id (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商分类关联表';
-- 4. ä¾›åº”商准入申请表
CREATE TABLE IF NOT EXISTS srm_supplier_apply (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '申请ID',
    apply_no VARCHAR(64) NOT NULL COMMENT '申请编号',
    supplier_id BIGINT DEFAULT NULL COMMENT '供应商ID(新建为空,准入后回填)',
    supplier_name VARCHAR(255) NOT NULL COMMENT '供应商名称',
    supplier_type VARCHAR(50) DEFAULT NULL COMMENT '供应商类型',
    company_nature VARCHAR(50) DEFAULT NULL COMMENT '企业性质',
    registered_capital DECIMAL(24,6) DEFAULT NULL COMMENT '注册资金(万元)',
    business_license_no VARCHAR(100) DEFAULT NULL COMMENT '营业执照编号',
    tax_no VARCHAR(100) DEFAULT NULL COMMENT '税号',
    address VARCHAR(500) DEFAULT NULL COMMENT '公司地址',
    contact_name VARCHAR(50) DEFAULT NULL COMMENT '联系人',
    contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
    contact_email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
    apply_user_id BIGINT DEFAULT NULL COMMENT '申请人ID',
    apply_user_name VARCHAR(50) DEFAULT NULL COMMENT '申请人姓名',
    apply_time DATETIME DEFAULT NULL COMMENT '申请时间',
    apply_status INT NOT NULL DEFAULT 0 COMMENT '审核状态(0待提交 10待采购审核 20待质量审核 30待财务审核 40已通过 50已拒绝)',
    purchase_reviewer_id BIGINT DEFAULT NULL COMMENT '采购审核人ID',
    purchase_review_time DATETIME DEFAULT NULL COMMENT '采购审核时间',
    purchase_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '采购审核意见',
    quality_reviewer_id BIGINT DEFAULT NULL COMMENT '质量审核人ID',
    quality_review_time DATETIME DEFAULT NULL COMMENT '质量审核时间',
    quality_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '质量审核意见',
    finance_reviewer_id BIGINT DEFAULT NULL COMMENT '财务审核人ID',
    finance_review_time DATETIME DEFAULT NULL COMMENT '财务审核时间',
    finance_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '财务审核意见',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_apply_no (apply_no),
    KEY idx_supplier_id (supplier_id),
    KEY idx_apply_status (apply_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商准入申请表';
-- 5. ä¾›åº”商资质证书表
CREATE TABLE IF NOT EXISTS srm_supplier_certificate (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '证书ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    certificate_type INT NOT NULL COMMENT '证书类型(1营业执照 2认证证书 3检测报告 4其他)',
    certificate_name VARCHAR(255) NOT NULL COMMENT '证书名称',
    certificate_no VARCHAR(100) DEFAULT NULL COMMENT '证书编号',
    file_name VARCHAR(255) DEFAULT NULL COMMENT '文件名称',
    file_url VARCHAR(500) DEFAULT NULL COMMENT '文件地址',
    issuing_authority VARCHAR(255) DEFAULT NULL COMMENT '颁发机构',
    valid_start_date DATE DEFAULT NULL COMMENT '有效开始日期',
    valid_end_date DATE DEFAULT NULL COMMENT '有效结束日期',
    status INT NOT NULL DEFAULT 0 COMMENT '状态(0有效 1过期 2即将过期)',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_certificate_type (certificate_type),
    KEY idx_valid_end_date (valid_end_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商资质证书表';
-- 6. ä¾›åº”商绩效评价表
CREATE TABLE IF NOT EXISTS srm_supplier_evaluation (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '评价ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    evaluation_period VARCHAR(50) NOT NULL COMMENT '评价周期(如:2026-Q1)',
    quality_score DECIMAL(5,2) DEFAULT NULL COMMENT '质量评分(满分100)',
    quality_pass_rate DECIMAL(5,2) DEFAULT NULL COMMENT '来料合格率(%)',
    quality_defect_count INT DEFAULT 0 COMMENT '不良次数',
    delivery_score DECIMAL(5,2) DEFAULT NULL COMMENT '交付评分(满分100)',
    delivery_on_time_rate DECIMAL(5,2) DEFAULT NULL COMMENT '准时交付率(%)',
    delivery_delay_count INT DEFAULT 0 COMMENT '延迟次数',
    price_score DECIMAL(5,2) DEFAULT NULL COMMENT '价格评分(满分100)',
    service_score DECIMAL(5,2) DEFAULT NULL COMMENT '服务评分(满分100)',
    service_response_speed INT DEFAULT NULL COMMENT '响应速度(评分1-10)',
    composite_score DECIMAL(5,2) DEFAULT NULL COMMENT '综合评分(自动计算)',
    evaluation_level VARCHAR(20) DEFAULT NULL COMMENT '评价等级(A/B/C/D)',
    evaluator_id BIGINT DEFAULT NULL COMMENT '评价人ID',
    evaluation_time DATETIME DEFAULT NULL COMMENT '评价时间',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_evaluation_period (evaluation_period),
    KEY idx_composite_score (composite_score)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商绩效评价表';
-- 7. æ‹›æ ‡é¡¹ç›®è¡¨
CREATE TABLE IF NOT EXISTS srm_tender_project (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '招标项目ID',
    tender_no VARCHAR(64) NOT NULL COMMENT '招标编号',
    tender_name VARCHAR(255) NOT NULL COMMENT '招标名称',
    tender_type INT NOT NULL COMMENT '招标类型(1公开招标 2邀请招标 3竞争性谈判)',
    purchase_org VARCHAR(100) DEFAULT NULL COMMENT '采购组织',
    project_leader_id BIGINT DEFAULT NULL COMMENT '项目负责人ID',
    project_leader_name VARCHAR(50) DEFAULT NULL COMMENT '项目负责人姓名',
    tender_status INT NOT NULL DEFAULT 0 COMMENT '招标状态(0草稿 1发布中 2投标中 3评标中 4已定标 5已关闭)',
    budget_amount DECIMAL(24,6) DEFAULT NULL COMMENT '预算金额',
    start_time DATETIME DEFAULT NULL COMMENT '开始时间',
    end_time DATETIME DEFAULT NULL COMMENT '截止时间',
    bid_open_time DATETIME DEFAULT NULL COMMENT '预计开标时间',
    tender_desc TEXT DEFAULT NULL COMMENT '招标说明',
    qualification_requirements TEXT DEFAULT NULL COMMENT '资质要求',
    purchase_request_id BIGINT DEFAULT NULL COMMENT '关联采购申请ID',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_tender_no (tender_no),
    KEY idx_tender_status (tender_status),
    KEY idx_purchase_request_id (purchase_request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='招标项目表';
-- 8. æ‹›æ ‡ç‰©æ–™æ˜Žç»†è¡¨
CREATE TABLE IF NOT EXISTS srm_tender_material (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '物料明细ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    product_id BIGINT DEFAULT NULL COMMENT '物料ID(关联MDM物料)',
    product_code VARCHAR(64) DEFAULT NULL COMMENT '物料编码',
    product_name VARCHAR(255) NOT NULL COMMENT '物料名称',
    product_spec VARCHAR(200) DEFAULT NULL COMMENT '规格型号',
    quantity DECIMAL(24,6) NOT NULL COMMENT '数量',
    unit VARCHAR(20) NOT NULL COMMENT '单位',
    tech_requirement TEXT DEFAULT NULL COMMENT '技术要求',
    estimated_price DECIMAL(24,6) DEFAULT NULL COMMENT '预估单价',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_product_code (product_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='招标物料明细表';
-- 9. æŠ•标记录表
CREATE TABLE IF NOT EXISTS srm_tender_bid (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '投标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_no VARCHAR(64) NOT NULL COMMENT '投标编号',
    bid_status INT NOT NULL DEFAULT 0 COMMENT '投标状态(0报名 10已投标 20撤标 30中标 40未中标)',
    bid_time DATETIME DEFAULT NULL COMMENT '投标时间',
    bid_total_amount DECIMAL(24,6) DEFAULT NULL COMMENT '投标总金额',
    bid_file_name VARCHAR(255) DEFAULT NULL COMMENT '投标文件名称',
    bid_file_url VARCHAR(500) DEFAULT NULL COMMENT '投标文件地址',
    withdraw_reason VARCHAR(500) DEFAULT NULL COMMENT '撤标原因',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_bid_no (bid_no),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_status (bid_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='投标记录表';
-- 10. ä¾›åº”商报价明细表
CREATE TABLE IF NOT EXISTS srm_supplier_quote (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '报价ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_id BIGINT DEFAULT NULL COMMENT '投标记录ID',
    tender_material_id BIGINT NOT NULL COMMENT '招标物料ID',
    quote_price DECIMAL(24,6) NOT NULL COMMENT '报价价格',
    tax_rate DECIMAL(5,2) DEFAULT NULL COMMENT '税率(%)',
    delivery_cycle INT DEFAULT NULL COMMENT '交货周期(天)',
    payment_terms VARCHAR(500) DEFAULT NULL COMMENT '付款条件',
    warranty_period INT DEFAULT NULL COMMENT '质保期(月)',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_id (bid_id),
    KEY idx_tender_material_id (tender_material_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商报价明细表';
-- 11. å¼€æ ‡è®°å½•表
CREATE TABLE IF NOT EXISTS srm_bid_open (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '开标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    open_time DATETIME NOT NULL COMMENT '开标时间',
    open_person_id BIGINT DEFAULT NULL COMMENT '开标人员ID',
    open_person_name VARCHAR(50) DEFAULT NULL COMMENT '开标人员姓名',
    open_record TEXT DEFAULT NULL COMMENT '开标记录',
    attachment_name VARCHAR(255) DEFAULT NULL COMMENT '附件名称',
    attachment_url VARCHAR(500) DEFAULT NULL COMMENT '附件地址',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='开标记录表';
-- 12. è¯„标记录表
CREATE TABLE IF NOT EXISTS srm_bid_evaluation (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '评标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_id BIGINT NOT NULL COMMENT '投标记录ID',
    price_score DECIMAL(5,2) DEFAULT NULL COMMENT '价格评分(满分40)',
    tech_score DECIMAL(5,2) DEFAULT NULL COMMENT '技术评分(满分30)',
    delivery_score DECIMAL(5,2) DEFAULT NULL COMMENT '交付评分(满分20)',
    service_score DECIMAL(5,2) DEFAULT NULL COMMENT '服务评分(满分10)',
    composite_score DECIMAL(5,2) DEFAULT NULL COMMENT '综合评分(自动计算=ä»·æ ¼+技术+交付+服务)',
    ranking INT DEFAULT NULL COMMENT '排名',
    evaluator_id BIGINT DEFAULT NULL COMMENT '评标人ID',
    evaluator_name VARCHAR(50) DEFAULT NULL COMMENT '评标人姓名',
    evaluation_opinion VARCHAR(500) DEFAULT NULL COMMENT '评标意见',
    evaluation_time DATETIME DEFAULT NULL COMMENT '评标时间',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_id (bid_id),
    KEY idx_ranking (ranking)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评标记录表';
-- 13. å®šæ ‡è®°å½•表
CREATE TABLE IF NOT EXISTS srm_bid_award (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '定标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    supplier_id BIGINT NOT NULL COMMENT '中标供应商ID',
    bid_id BIGINT NOT NULL COMMENT '中标投标ID',
    award_no VARCHAR(64) NOT NULL COMMENT '中标通知书编号',
    award_status INT NOT NULL DEFAULT 0 COMMENT '定标状态(0待审批 10已定标 20已生成订单)',
    award_amount DECIMAL(24,6) NOT NULL COMMENT '中标金额',
    award_time DATETIME DEFAULT NULL COMMENT '定标时间',
    notice_file_name VARCHAR(255) DEFAULT NULL COMMENT '中标通知书文件名',
    notice_file_url VARCHAR(500) DEFAULT NULL COMMENT '中标通知书文件地址',
    purchase_order_id BIGINT DEFAULT NULL COMMENT '关联生成的采购订单ID',
    purchase_order_no VARCHAR(64) DEFAULT NULL COMMENT '关联生成的采购订单号',
    approver_id BIGINT DEFAULT NULL COMMENT '审批人ID',
    approval_time DATETIME DEFAULT NULL COMMENT '审批时间',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_award_no (award_no),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_purchase_order_id (purchase_order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='定标记录表';
yudao-module-srm/src/main/resources/sql/srm_menu_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
-- =====================================================
-- SRM æ¨¡å—菜单数据
-- =====================================================
-- 1. äºŒçº§ç›®å½•:SRM ä¾›åº”商管理(挂在采购管理下)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602524, 'SRM ä¾›åº”商管理', '', 1, 5, 2602, 'srm', 'ep:avatar', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 2. ä¾›åº”商档案
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602525, '供应商档案', '', 2, 10, 602524, 'supplier', 'ep:document', 'srm/supplier/index', 'SrmSupplier', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602526, '供应商查询', 'srm:supplier:query', 3, 1, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602527, '供应商创建', 'srm:supplier:create', 3, 2, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602528, '供应商更新', 'srm:supplier:update', 3, 3, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602529, '供应商删除', 'srm:supplier:delete', 3, 4, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 3. ä¾›åº”商准入
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602530, '供应商准入', '', 2, 20, 602524, 'supplier-apply', 'ep:checked', 'srm/supplierApply/index', 'SrmSupplierApply', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602531, '准入查询', 'srm:supplier-apply:query', 3, 1, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602532, '准入创建', 'srm:supplier-apply:create', 3, 2, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602533, '准入更新', 'srm:supplier-apply:update', 3, 3, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602534, '准入删除', 'srm:supplier-apply:delete', 3, 4, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 4. ä¾›åº”商资质
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602535, '供应商资质', '', 2, 30, 602524, 'supplier-certificate', 'ep:medal', 'srm/supplierCertificate/index', 'SrmSupplierCertificate', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602536, '资质查询', 'srm:supplier-certificate:query', 3, 1, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602537, '资质创建', 'srm:supplier-certificate:create', 3, 2, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602538, '资质更新', 'srm:supplier-certificate:update', 3, 3, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602539, '资质删除', 'srm:supplier-certificate:delete', 3, 4, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 5. ä¾›åº”商绩效
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602540, '供应商绩效', '', 2, 40, 602524, 'supplier-evaluation', 'ep:data-analysis', 'srm/supplierEvaluation/index', 'SrmSupplierEvaluation', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602541, '评价查询', 'srm:supplier-evaluation:query', 3, 1, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602542, '评价创建', 'srm:supplier-evaluation:create', 3, 2, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602543, '评价更新', 'srm:supplier-evaluation:update', 3, 3, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602544, '评价删除', 'srm:supplier-evaluation:delete', 3, 4, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 6. æ‹›æŠ•标管理
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602545, '招投标管理', '', 2, 50, 602524, 'tender', 'ep:files', 'srm/tender/index', 'SrmTender', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
-- æ‹›æ ‡é¡¹ç›®
(602546, '招标查询', 'srm:tender-project:query', 3, 1, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602547, '招标创建', 'srm:tender-project:create', 3, 2, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602548, '招标更新', 'srm:tender-project:update', 3, 3, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602549, '招标删除', 'srm:tender-project:delete', 3, 4, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æŠ•æ ‡
(602550, '投标查询', 'srm:tender-bid:query', 3, 5, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602551, '投标创建', 'srm:tender-bid:create', 3, 6, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602552, '投标更新', 'srm:tender-bid:update', 3, 7, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æŠ¥ä»·
(602553, '报价查询', 'srm:supplier-quote:query', 3, 8, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602554, '报价创建', 'srm:supplier-quote:create', 3, 9, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602555, '报价更新', 'srm:supplier-quote:update', 3, 10, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- è¯„æ ‡
(602556, '评标查询', 'srm:bid-evaluation:query', 3, 11, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602557, '评标创建', 'srm:bid-evaluation:create', 3, 12, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602558, '评标更新', 'srm:bid-evaluation:update', 3, 13, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å¼€æ ‡
(602559, '开标查询', 'srm:bid-open:query', 3, 14, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602560, '开标创建', 'srm:bid-open:create', 3, 15, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å®šæ ‡
(602561, '定标查询', 'srm:bid-award:query', 3, 16, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602562, '定标创建', 'srm:bid-award:create', 3, 17, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602563, '定标更新', 'srm:bid-award:update', 3, 18, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 7. ç»Ÿè®¡åˆ†æž
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602569, '统计分析', '', 2, 70, 602524, 'statistics', 'ep:trend-charts', 'srm/statistics/index', 'SrmStatistics', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602570, '统计查询', 'srm:statistics:query', 3, 1, 602569, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
yudao-module-srm/src/main/resources/sql/srm_test_data.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
-- =====================================================
-- SRM æ¨¡å—测试数据
-- è¿è¡Œå‰è¯·å…ˆç¡®ä¿ srm_init.sql å·²æ‰§è¡Œ
-- =====================================================
-- 1. ä¾›åº”商测试数据
INSERT INTO srm_supplier (id, code, name, type, status, contact_name, contact_mobile, contact_email, address, bank_name, bank_account, tax_no, remark, create_time, update_time, creator, updater, deleted)
VALUES
(1001, 'SUP-20240001', '江苏智能制造有限公司', 1, 1, '张三', '13800001111', 'zhangsan@test.com', '江苏省南京市江宁区xxè·¯100号', '中国银行南京分行', '6222024301000000001', '91320115MA1XXXXXX1', '优质供应商', NOW(), NOW(), 'admin', 'admin', 0),
(1002, 'SUP-20240002', '上海贸易进出口公司', 2, 1, '李四', '13800002222', 'lisi@test.com', '上海市浦东新区xxè·¯200号', '工商银行上海分行', '1002024301000000002', '91310115MA2XXXXXX2', '长期合作', NOW(), NOW(), 'admin', 'admin', 0),
(1003, 'SUP-20240003', '深圳科技服务有限公司', 3, 1, '王五', '13800003333', 'wangwu@test.com', '深圳市南山区xxè·¯300号', '招商银行深圳分行', '6225884301000000003', '91440300MA3XXXXXX3', '新供应商', NOW(), NOW(), 'admin', 'admin', 0),
(1004, 'SUP-20240004', '浙江精密机械有限公司', 1, 0, '赵六', '13800004444', 'zhaoliu@test.com', '浙江省杭州市西湖区xxè·¯400号', '建设银行杭州分行', '6227004301000000004', '91330100MA4XXXXXX4', '暂停合作', NOW(), NOW(), 'admin', 'admin', 0);
-- 2. ä¾›åº”商联系人
INSERT INTO srm_supplier_contact (id, supplier_id, name, mobile, email, position, is_primary, remark, create_time, update_time, creator, updater, deleted)
VALUES
(2001, 1001, '张三', '13800001111', 'zhangsan@test.com', '采购经理', 1, '主要联系人', NOW(), NOW(), 'admin', 'admin', 0),
(2002, 1001, '张三丰', '13800001112', 'zhangsanfeng@test.com', '技术总监', 0, '技术对接', NOW(), NOW(), 'admin', 'admin', 0),
(2003, 1002, '李四', '13800002222', 'lisi@test.com', '总经理', 1, NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 3. ä¾›åº”商分类关联
INSERT INTO srm_supplier_category (id, supplier_id, category_id, category_name, create_time, update_time, creator, updater, deleted)
VALUES
(3001, 1001, 1, '机械加工', NOW(), NOW(), 'admin', 'admin', 0),
(3002, 1001, 2, '精密制造', NOW(), NOW(), 'admin', 'admin', 0),
(3003, 1002, 3, '贸易服务', NOW(), NOW(), 'admin', 'admin', 0);
-- 4. ä¾›åº”商准入申请
INSERT INTO srm_supplier_apply (id, apply_no, supplier_name, contact_name, contact_mobile, contact_email, address, bank_name, bank_account, tax_no, apply_status, audit_comment, remark, create_time, update_time, creator, updater, deleted)
VALUES
(4001, 'AP2024010001', '江苏智能制造有限公司', '张三', '13800001111', 'zhangsan@test.com', '江苏省南京市江宁区xxè·¯100号', '中国银行南京分行', '6222024301000000001', '91320115MA1XXXXXX1', 4, '资质齐全,审批通过', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(4002, 'AP2024020002', '北京新科技有限公司', '刘七', '13800005555', 'liuqi@test.com', '北京市海淀区xxè·¯500号', '农业银行北京分行', '6228484301000000005', '91110108MA5XXXXXX5', 1, NULL, '待审核', NOW(), NOW(), 'admin', 'admin', 0);
-- 5. ä¾›åº”商资质证书
INSERT INTO srm_supplier_certificate (id, supplier_id, certificate_name, certificate_no, certificate_type, issuing_authority, issue_date, expire_date, remark, create_time, update_time, creator, updater, deleted)
VALUES
(5001, 1001, 'ISO9001质量管理体系证书', 'ISO9001-2024-001', 1, 'SGS认证机构', '2024-01-15', '2027-01-14', '三年有效期', NOW(), NOW(), 'admin', 'admin', 0),
(5002, 1001, 'ISO14001环境管理体系证书', 'ISO14001-2024-002', 2, 'TUV认证机构', '2024-03-01', '2027-02-28', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(5003, 1002, '进出口贸易许可证', 'TRADE-2024-001', 4, '商务部', '2024-02-01', '2025-01-31', '即将到期', NOW(), NOW(), 'admin', 'admin', 0);
-- 6. ä¾›åº”商绩效评价
INSERT INTO srm_supplier_evaluation (id, supplier_id, evaluation_period, quality_score, delivery_score, price_score, service_score, composite_score, grade, remark, create_time, update_time, creator, updater, deleted)
VALUES
(6001, 1001, '2024-Q1', 92, 88, 85, 90, 89, 'B', '质量稳定,交付及时', NOW(), NOW(), 'admin', 'admin', 0),
(6002, 1001, '2024-Q2', 95, 92, 88, 93, 92, 'A', '各项指标优秀', NOW(), NOW(), 'admin', 'admin', 0),
(6003, 1002, '2024-Q1', 80, 75, 90, 85, 81, 'B', '价格有优势,交付略慢', NOW(), NOW(), 'admin', 'admin', 0);
-- 7. æ‹›æ ‡é¡¹ç›®
INSERT INTO srm_tender_project (id, tender_no, tender_name, tender_type, purchase_org, project_leader_id, project_leader_name, tender_status, budget_amount, start_time, end_time, bid_open_time, tender_desc, qualification_requirements, purchase_request_id, create_time, update_time, creator, updater, deleted)
VALUES
(7001, 'TEN-202401001', '2024å¹´Q1原材料采购招标', 1, '采购部', 1, '管理员', 4, 5000000.00, '2024-01-01 00:00:00', '2024-01-31 23:59:59', '2024-02-01 10:00:00', '采购不锈钢板材200吨', '1. æ³¨å†Œèµ„金≥500万 2. å…·å¤‡ISO9001认证', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(7002, 'TEN-202402002', '2024å¹´Q2设备采购招标', 2, '设备部', 2, '王经理', 1, 2000000.00, '2024-04-01 00:00:00', '2024-04-30 23:59:59', '2024-05-01 14:00:00', '采购数控机床5台', '1. å…·å¤‡è®¾å¤‡ä»£ç†èµ„è´¨ 2. æä¾›å”®åŽæœåŠ¡æ‰¿è¯º', NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 8. æ‹›æ ‡ç‰©æ–™
INSERT INTO srm_tender_material (id, tender_project_id, item_code, item_name, spec, unit, quantity, remark, create_time, update_time, creator, updater, deleted)
VALUES
(8001, 7001, 'MAT-001', '不锈钢板304', '2.0*1500*3000mm', '吨', 100, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(8002, 7001, 'MAT-002', '不锈钢板316L', '1.5*1500*3000mm', '吨', 100, '耐腐蚀要求', NOW(), NOW(), 'admin', 'admin', 0),
(8003, 7002, 'MAT-003', '数控机床CNC-500', '加工范围500*400mm', '台', 5, '含安装调试', NOW(), NOW(), 'admin', 'admin', 0);
-- 9. æŠ•标记录
INSERT INTO srm_tender_bid (id, tender_project_id, supplier_id, bid_no, bid_status, bid_time, bid_total_amount, withdraw_reason, create_time, update_time, creator, updater, deleted)
VALUES
(9001, 7001, 1001, 'BID-202401-001', 1, '2024-01-15 10:00:00', 4800000.00, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(9002, 7001, 1002, 'BID-202401-002', 1, '2024-01-20 14:00:00', 4900000.00, NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 10. ä¾›åº”商报价
INSERT INTO srm_supplier_quote (id, bid_id, material_id, unit_price, total_price, delivery_days, remark, create_time, update_time, creator, updater, deleted)
VALUES
(10001, 9001, 8001, 22000.00, 2200000.00, 30, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(10002, 9001, 8002, 26000.00, 2600000.00, 30, '含运费', NOW(), NOW(), 'admin', 'admin', 0),
(10003, 9002, 8001, 23000.00, 2300000.00, 25, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(10004, 9002, 8002, 26000.00, 2600000.00, 28, NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 11. å¼€æ ‡è®°å½•
INSERT INTO srm_bid_open (id, tender_project_id, open_no, open_time, bid_count, supplier_count, open_status, remark, create_time, update_time, creator, updater, deleted)
VALUES
(11001, 7001, 'OPEN-20240201', '2024-02-01 10:00:00', 2, 2, 1, '投标截止,共2家供应商参与', NOW(), NOW(), 'admin', 'admin', 0);
-- 12. è¯„标记录
INSERT INTO srm_bid_evaluation (id, tender_project_id, bid_id, technical_score, commercial_score, composite_score, rank, comment, create_time, update_time, creator, updater, deleted)
VALUES
(12001, 7001, 9001, 88, 85, 86, 1, '技术方案优秀,价格合理', NOW(), NOW(), 'admin', 'admin', 0),
(12002, 7001, 9002, 82, 90, 87, 2, '价格有优势,技术方案一般', NOW(), NOW(), 'admin', 'admin', 0);
-- 13. å®šæ ‡è®°å½•
INSERT INTO srm_bid_award (id, tender_project_id, supplier_id, bid_id, award_no, award_status, award_amount, award_time, purchase_order_id, purchase_order_no, remark, create_time, update_time, creator, updater, deleted)
VALUES
(13001, 7001, 1001, 9001, 'AWARD-202402-001', 1, 4800000.00, '2024-02-05 16:00:00', NULL, NULL, '综合评分第一', NOW(), NOW(), 'admin', 'admin', 0);
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -25,6 +26,7 @@
    private String refreshToken;
    @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private LocalDateTime expiresTime;
}
yudao-server/pom.xml
@@ -143,6 +143,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- SRM ä¾›åº”商关系管理模块 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-srm</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- spring boot é…ç½®æ‰€éœ€ä¾èµ– -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplier/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
import request from '@/config/axios'
export interface SrmSupplierVO {
  id: number
  code: string
  name: string
  shortName: string
  type: string
  companyNature: string
  registeredCapital: number
  businessLicenseNo: string
  taxNo: string
  legalPerson: string
  address: string
  website: string
  contactName: string
  contactMobile: string
  contactEmail: string
  bankName: string
  bankAccount: string
  status: number
  remark: string
  erpSupplierId: number
  createTime: string
}
export interface SrmSupplierContactVO {
  id: number
  supplierId: number
  name: string
  position: string
  mobile: string
  telephone: string
  email: string
  wechat: string
  isDefault: boolean
  sort: number
  createTime: string
}
export const SrmSupplierApi = {
  // åˆ†é¡µæŸ¥è¯¢
  getSupplierPage: async (params: any) =>
    request.get({ url: '/srm/supplier/page', params }),
  // èŽ·å–è¯¦æƒ…
  getSupplier: async (id: number) =>
    request.get({ url: '/srm/supplier/get', params: { id } }),
  // åˆ›å»º
  createSupplier: async (data: any) =>
    request.post({ url: '/srm/supplier/create', data }),
  // æ›´æ–°
  updateSupplier: async (data: any) =>
    request.put({ url: '/srm/supplier/update', data }),
  // åˆ é™¤
  deleteSupplier: async (id: number) =>
    request.delete({ url: '/srm/supplier/delete', params: { id } }),
  // ç²¾ç®€åˆ—表(下拉选择)
  getSupplierSimpleList: async () =>
    request.get({ url: '/srm/supplier/simple-list' }),
  // å¯¼å‡º Excel
  exportSupplierExcel: async (params: any) =>
    request.download({ url: '/srm/supplier/export-excel', params }),
  // è”系人列表
  getContactList: async (supplierId: number) =>
    request.get({ url: '/srm/supplier-contact/list-by-supplier', params: { supplierId } }),
  // åˆ›å»ºè”系人
  createContact: async (data: any) =>
    request.post({ url: '/srm/supplier-contact/create', data }),
  // æ›´æ–°è”系人
  updateContact: async (data: any) =>
    request.put({ url: '/srm/supplier-contact/update', data }),
  // åˆ é™¤è”系人
  deleteContact: async (id: number) =>
    request.delete({ url: '/srm/supplier-contact/delete', params: { id } })
}
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplierApply/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
import request from '@/config/axios'
export interface SrmSupplierApplyVO {
  id: number
  applyNo: string
  supplierId: number
  supplierName: string
  supplierType: string
  companyNature: string
  registeredCapital: number
  businessLicenseNo: string
  taxNo: string
  address: string
  contactName: string
  contactMobile: string
  contactEmail: string
  applyUserId: number
  applyUserName: string
  applyTime: string
  applyStatus: number
  purchaseReviewerId: number
  purchaseReviewTime: string
  purchaseReviewOpinion: string
  qualityReviewerId: number
  qualityReviewTime: string
  qualityReviewOpinion: string
  financeReviewerId: number
  financeReviewTime: string
  financeReviewOpinion: string
  createTime: string
}
export const SrmSupplierApplyApi = {
  // åˆ†é¡µæŸ¥è¯¢
  getApplyPage: async (params: any) =>
    request.get({ url: '/srm/supplier-apply/page', params }),
  // èŽ·å–è¯¦æƒ…
  getApply: async (id: number) =>
    request.get({ url: '/srm/supplier-apply/get', params: { id } }),
  // åˆ›å»º
  createApply: async (data: any) =>
    request.post({ url: '/srm/supplier-apply/create', data }),
  // æ›´æ–°
  updateApply: async (data: any) =>
    request.put({ url: '/srm/supplier-apply/update', data }),
  // åˆ é™¤
  deleteApply: async (id: number) =>
    request.delete({ url: '/srm/supplier-apply/delete', params: { id } }),
  // æäº¤ï¼ˆè¿›å…¥å®¡æ ¸æµç¨‹ï¼‰
  submitApply: async (id: number) =>
    request.put({ url: '/srm/supplier-apply/submit', params: { id } }),
  // é‡‡è´­å®¡æ ¸
  reviewPurchase: async (data: { id: number, opinion: string }) =>
    request.put({ url: '/srm/supplier-apply/review-purchase', data }),
  // è´¨é‡å®¡æ ¸
  reviewQuality: async (data: { id: number, opinion: string }) =>
    request.put({ url: '/srm/supplier-apply/review-quality', data }),
  // è´¢åŠ¡å®¡æ ¸
  reviewFinance: async (data: { id: number, opinion: string }) =>
    request.put({ url: '/srm/supplier-apply/review-finance', data })
}
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplierEvaluation/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import request from '@/config/axios'
export interface SrmSupplierEvaluationVO {
  id: number
  supplierId: number
  evaluationPeriod: string
  qualityScore: number
  qualityPassRate: number
  qualityDefectCount: number
  deliveryScore: number
  deliveryOnTimeRate: number
  deliveryDelayCount: number
  priceScore: number
  serviceScore: number
  serviceResponseSpeed: number
  compositeScore: number
  evaluationLevel: string
  evaluatorId: number
  evaluationTime: string
  remark: string
  createTime: string
}
export const SrmSupplierEvaluationApi = {
  // åˆ†é¡µæŸ¥è¯¢
  getEvaluationPage: async (params: any) =>
    request.get({ url: '/srm/supplier-evaluation/page', params }),
  // èŽ·å–è¯¦æƒ…
  getEvaluation: async (id: number) =>
    request.get({ url: '/srm/supplier-evaluation/get', params: { id } }),
  // åˆ›å»º
  createEvaluation: async (data: any) =>
    request.post({ url: '/srm/supplier-evaluation/create', data }),
  // æ›´æ–°
  updateEvaluation: async (data: any) =>
    request.put({ url: '/srm/supplier-evaluation/update', data }),
  // åˆ é™¤
  deleteEvaluation: async (id: number) =>
    request.delete({ url: '/srm/supplier-evaluation/delete', params: { id } })
}
yudao-ui/yudao-ui-admin-vue3/src/api/srm/tender/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import request from '@/config/axios'
export interface SrmTenderProjectVO {
  id: number
  tenderNo: string
  tenderName: string
  tenderType: number
  purchaseOrg: string
  projectLeaderId: number
  projectLeaderName: string
  tenderStatus: number
  budgetAmount: number
  startTime: string
  endTime: string
  bidOpenTime: string
  tenderDesc: string
  qualificationRequirements: string
  purchaseRequestId: number
  createTime: string
}
export const SrmTenderApi = {
  // åˆ†é¡µæŸ¥è¯¢
  getTenderPage: async (params: any) =>
    request.get({ url: '/srm/tender-project/page', params }),
  // èŽ·å–è¯¦æƒ…
  getTender: async (id: number) =>
    request.get({ url: '/srm/tender-project/get', params: { id } }),
  // åˆ›å»º
  createTender: async (data: any) =>
    request.post({ url: '/srm/tender-project/create', data }),
  // æ›´æ–°
  updateTender: async (data: any) =>
    request.put({ url: '/srm/tender-project/update', data }),
  // åˆ é™¤
  deleteTender: async (id: number) =>
    request.delete({ url: '/srm/tender-project/delete', params: { id } }),
  // å‘布
  publishTender: async (id: number) =>
    request.put({ url: '/srm/tender-project/publish', params: { id } }),
  // å…³é—­
  closeTender: async (id: number) =>
    request.put({ url: '/srm/tender-project/close', params: { id } }),
  // å¼€å§‹æŠ•æ ‡
  startBidding: async (id: number) =>
    request.put({ url: '/srm/tender-project/start-bidding', params: { id } }),
  // å¼€å§‹è¯„æ ‡
  startEvaluation: async (id: number) =>
    request.put({ url: '/srm/tender-project/start-evaluation', params: { id } })
}
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplier/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,331 @@
<template>
  <ContentWrap>
    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="88px">
      <el-form-item label="供应商编码" prop="code">
        <el-input v-model="queryParams.code" placeholder="请输入编码" clearable @keyup.enter="handleQuery" class="!w-200px" />
      </el-form-item>
      <el-form-item label="供应商名称" prop="name">
        <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-200px" />
      </el-form-item>
      <el-form-item label="供应商类型" prop="type">
        <el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-160px">
          <el-option label="生产型" value="1" />
          <el-option label="贸易型" value="2" />
          <el-option label="服务型" value="3" />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-140px">
          <el-option label="启用" :value="0" />
          <el-option label="禁用" :value="1" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> æœç´¢</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> é‡ç½®</el-button>
        <el-button type="primary" plain @click="openForm()" v-hasPermi="['srm:supplier:create']">
          <Icon icon="ep:plus" class="mr-5px" /> æ–°å¢ž
        </el-button>
        <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['srm:supplier:export']">
          <Icon icon="ep:download" class="mr-5px" /> å¯¼å‡º
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <ContentWrap>
    <el-table v-loading="loading" :data="list" stripe>
      <el-table-column label="供应商编码" align="center" prop="code" min-width="140" />
      <el-table-column label="供应商名称" align="center" prop="name" min-width="180" show-overflow-tooltip />
      <el-table-column label="简称" align="center" prop="shortName" min-width="120" />
      <el-table-column label="类型" align="center" prop="type" min-width="100">
        <template #default="scope">
          <el-tag v-if="scope.row.type === '1'" type="primary">生产型</el-tag>
          <el-tag v-else-if="scope.row.type === '2'" type="success">贸易型</el-tag>
          <el-tag v-else-if="scope.row.type === '3'" type="warning">服务型</el-tag>
          <span v-else>{{ scope.row.type }}</span>
        </template>
      </el-table-column>
      <el-table-column label="联系人" align="center" prop="contactName" min-width="100" />
      <el-table-column label="联系电话" align="center" prop="contactMobile" min-width="130" />
      <el-table-column label="邮箱" align="center" prop="contactEmail" min-width="160" show-overflow-tooltip />
      <el-table-column label="企业性质" align="center" prop="companyNature" min-width="100" />
      <el-table-column label="状态" align="center" prop="status" min-width="80">
        <template #default="scope">
          <el-switch :model-value="scope.row.status === 0" @change="(val: boolean) => handleStatusChange(scope.row, val)" active-text="启用" inactive-text="禁用" />
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
      <el-table-column label="操作" align="center" width="200" fixed="right">
        <template #default="scope">
          <el-button link type="primary" @click="openDetail(scope.row.id)" v-hasPermi="['srm:supplier:query']">详情</el-button>
          <el-button link type="primary" @click="openForm(scope.row.id)" v-hasPermi="['srm:supplier:update']">编辑</el-button>
          <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['srm:supplier:delete']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
  </ContentWrap>
  <!-- æ–°å¢ž/编辑对话框 -->
  <el-dialog :title="dialogTitle" v-model="dialogVisible" width="750px" @close="resetForm">
    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="供应商编码" prop="code">
            <el-input v-model="formData.code" placeholder="请输入编码" :disabled="!!formData.id" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="供应商名称" prop="name">
            <el-input v-model="formData.name" placeholder="请输入名称" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="供应商简称" prop="shortName">
            <el-input v-model="formData.shortName" placeholder="请输入简称" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="供应商类型" prop="type">
            <el-select v-model="formData.type" placeholder="请选择" class="!w-full">
              <el-option label="生产型" value="1" />
              <el-option label="贸易型" value="2" />
              <el-option label="服务型" value="3" />
              <el-option label="其他" value="4" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="企业性质" prop="companyNature">
            <el-select v-model="formData.companyNature" placeholder="请选择" class="!w-full">
              <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="registeredCapital">
            <el-input-number v-model="formData.registeredCapital" :min="0" :precision="2" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="营业执照编号" prop="businessLicenseNo">
            <el-input v-model="formData.businessLicenseNo" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="税号" prop="taxNo">
            <el-input v-model="formData.taxNo" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="法人代表" prop="legalPerson">
            <el-input v-model="formData.legalPerson" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="联系人" prop="contactName">
            <el-input v-model="formData.contactName" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="联系电话" prop="contactMobile">
            <el-input v-model="formData.contactMobile" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="邮箱" prop="contactEmail">
            <el-input v-model="formData.contactEmail" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="公司地址" prop="address">
            <el-input v-model="formData.address" placeholder="请输入地址" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="开户银行" prop="bankName">
            <el-input v-model="formData.bankName" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="银行账号" prop="bankAccount">
            <el-input v-model="formData.bankAccount" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="状态" prop="status">
            <el-radio-group v-model="formData.status">
              <el-radio :value="0">启用</el-radio>
              <el-radio :value="1">禁用</el-radio>
            </el-radio-group>
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="备注" prop="remark">
            <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
    </template>
  </el-dialog>
  <!-- è¯¦æƒ…对话框 -->
  <el-dialog :title="'供应商详情'" v-model="detailVisible" width="750px">
    <el-descriptions :column="2" border>
      <el-descriptions-item label="供应商编码">{{ detailData.code }}</el-descriptions-item>
      <el-descriptions-item label="供应商名称">{{ detailData.name }}</el-descriptions-item>
      <el-descriptions-item label="供应商简称">{{ detailData.shortName }}</el-descriptions-item>
      <el-descriptions-item label="供应商类型">{{ detailData.type }}</el-descriptions-item>
      <el-descriptions-item label="企业性质">{{ detailData.companyNature }}</el-descriptions-item>
      <el-descriptions-item label="注册资金(万元)">{{ detailData.registeredCapital }}</el-descriptions-item>
      <el-descriptions-item label="营业执照编号">{{ detailData.businessLicenseNo }}</el-descriptions-item>
      <el-descriptions-item label="税号">{{ detailData.taxNo }}</el-descriptions-item>
      <el-descriptions-item label="法人代表">{{ detailData.legalPerson }}</el-descriptions-item>
      <el-descriptions-item label="联系人">{{ detailData.contactName }}</el-descriptions-item>
      <el-descriptions-item label="联系电话">{{ detailData.contactMobile }}</el-descriptions-item>
      <el-descriptions-item label="邮箱">{{ detailData.contactEmail }}</el-descriptions-item>
      <el-descriptions-item label="开户银行">{{ detailData.bankName }}</el-descriptions-item>
      <el-descriptions-item label="银行账号">{{ detailData.bankAccount }}</el-descriptions-item>
      <el-descriptions-item label="公司地址" :span="2">{{ detailData.address }}</el-descriptions-item>
      <el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
    </el-descriptions>
  </el-dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import { SrmSupplierApi, SrmSupplierVO, SrmSupplierContactVO } from '@/api/srm/supplier'
defineOptions({ name: 'SrmSupplier' })
const message = useMessage()
const loading = ref(true)
const list = ref<SrmSupplierVO[]>([])
const total = ref(0)
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  code: undefined,
  name: undefined,
  type: undefined,
  status: undefined,
  contactName: undefined,
  contactMobile: undefined
})
const queryFormRef = ref()
const exportLoading = ref(false)
const getList = async () => {
  loading.value = true
  try {
    const data = await SrmSupplierApi.getSupplierPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}
// æ–°å¢ž/编辑对话框
const dialogVisible = ref(false)
const dialogTitle = ref('新增供应商')
const formLoading = ref(false)
const formData = ref<any>({})
const formRules = reactive({
  code: [{ required: true, message: '供应商编码不能为空', trigger: 'blur' }],
  name: [{ required: true, message: '供应商名称不能为空', trigger: 'blur' }]
})
const formRef = ref()
const openForm = async (id?: number) => {
  dialogTitle.value = id ? '编辑供应商' : '新增供应商'
  if (id) {
    const data = await SrmSupplierApi.getSupplier(id)
    formData.value = { ...data }
  } else {
    formData.value = { status: 0 }
  }
  dialogVisible.value = true
}
const resetForm = () => {
  formData.value = {}
  formRef.value?.resetFields()
}
const submitForm = async () => {
  await formRef.value.validate()
  formLoading.value = true
  try {
    if (formData.value.id) {
      await SrmSupplierApi.updateSupplier(formData.value)
    } else {
      await SrmSupplierApi.createSupplier(formData.value)
    }
    message.success(formData.value.id ? '更新成功' : '新增成功')
    dialogVisible.value = false
    await getList()
  } finally {
    formLoading.value = false
  }
}
const handleDelete = async (id: number) => {
  try {
    await message.delConfirm()
    await SrmSupplierApi.deleteSupplier(id)
    message.success('删除成功')
    await getList()
  } catch {}
}
const handleStatusChange = async (row: any, val: boolean) => {
  await SrmSupplierApi.updateSupplier({ ...row, status: val ? 0 : 1 })
  message.success('状态更新成功')
  await getList()
}
// è¯¦æƒ…对话框
const detailVisible = ref(false)
const detailData = ref<any>({})
const openDetail = async (id: number) => {
  detailData.value = await SrmSupplierApi.getSupplier(id)
  detailVisible.value = true
}
// å¯¼å‡º
const handleExport = async () => {
  try {
    await message.exportConfirm()
    exportLoading.value = true
    await SrmSupplierApi.exportSupplierExcel(queryParams)
  } catch {
  } finally {
    exportLoading.value = false
  }
}
onMounted(() => {
  getList()
})
</script>
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplierApply/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,295 @@
<template>
  <ContentWrap>
    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="88px">
      <el-form-item label="申请编号" prop="applyNo">
        <el-input v-model="queryParams.applyNo" placeholder="请输入编号" clearable @keyup.enter="handleQuery" class="!w-200px" />
      </el-form-item>
      <el-form-item label="供应商名称" prop="supplierName">
        <el-input v-model="queryParams.supplierName" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-200px" />
      </el-form-item>
      <el-form-item label="审核状态" prop="applyStatus">
        <el-select v-model="queryParams.applyStatus" placeholder="请选择" clearable class="!w-160px">
          <el-option label="待提交" :value="0" />
          <el-option label="待采购审核" :value="10" />
          <el-option label="待质量审核" :value="20" />
          <el-option label="待财务审核" :value="30" />
          <el-option label="已通过" :value="40" />
          <el-option label="已拒绝" :value="50" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> æœç´¢</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> é‡ç½®</el-button>
        <el-button type="primary" plain @click="openForm()" v-hasPermi="['srm:supplier-apply:create']">
          <Icon icon="ep:plus" class="mr-5px" /> æ–°å¢žç”³è¯·
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <ContentWrap>
    <el-table v-loading="loading" :data="list" stripe>
      <el-table-column label="申请编号" align="center" prop="applyNo" min-width="160" />
      <el-table-column label="供应商名称" align="center" prop="supplierName" min-width="180" show-overflow-tooltip />
      <el-table-column label="供应商类型" align="center" prop="supplierType" min-width="100" />
      <el-table-column label="联系人" align="center" prop="contactName" min-width="100" />
      <el-table-column label="联系电话" align="center" prop="contactMobile" min-width="130" />
      <el-table-column label="申请人" align="center" prop="applyUserName" min-width="100" />
      <el-table-column label="申请时间" align="center" prop="applyTime" :formatter="dateFormatter" width="160px" />
      <el-table-column label="审核状态" align="center" prop="applyStatus" min-width="120">
        <template #default="scope">
          <el-tag v-if="scope.row.applyStatus === 0" type="info">待提交</el-tag>
          <el-tag v-else-if="scope.row.applyStatus === 10" type="warning">待采购审核</el-tag>
          <el-tag v-else-if="scope.row.applyStatus === 20" type="warning">待质量审核</el-tag>
          <el-tag v-else-if="scope.row.applyStatus === 30" type="warning">待财务审核</el-tag>
          <el-tag v-else-if="scope.row.applyStatus === 40" type="success">已通过</el-tag>
          <el-tag v-else-if="scope.row.applyStatus === 50" type="danger">已拒绝</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="160px" />
      <el-table-column label="操作" align="center" width="260" fixed="right">
        <template #default="scope">
          <el-button v-if="scope.row.applyStatus === 0" link type="primary" @click="handleSubmit(scope.row.id)" v-hasPermi="['srm:supplier-apply:update']">提交</el-button>
          <el-button v-if="scope.row.applyStatus === 10" link type="primary" @click="openReview(scope.row.id, 'purchase')" v-hasPermi="['srm:supplier-apply:update']">采购审核</el-button>
          <el-button v-if="scope.row.applyStatus === 20" link type="primary" @click="openReview(scope.row.id, 'quality')" v-hasPermi="['srm:supplier-apply:update']">质量审核</el-button>
          <el-button v-if="scope.row.applyStatus === 30" link type="primary" @click="openReview(scope.row.id, 'finance')" v-hasPermi="['srm:supplier-apply:update']">财务审核</el-button>
          <el-button v-if="scope.row.applyStatus === 0" link type="primary" @click="openForm(scope.row.id)" v-hasPermi="['srm:supplier-apply:update']">编辑</el-button>
          <el-button link type="primary" @click="openDetail(scope.row.id)" v-hasPermi="['srm:supplier-apply:query']">详情</el-button>
          <el-button v-if="scope.row.applyStatus === 0" link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['srm:supplier-apply:delete']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
  </ContentWrap>
  <!-- æ–°å¢ž/编辑对话框 -->
  <el-dialog :title="'准入申请'" v-model="dialogVisible" width="700px" @close="resetForm">
    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="供应商名称" prop="supplierName">
            <el-input v-model="formData.supplierName" placeholder="请输入名称" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="供应商类型" prop="supplierType">
            <el-select v-model="formData.supplierType" placeholder="请选择" class="!w-full">
              <el-option label="生产型" value="1" />
              <el-option label="贸易型" value="2" />
              <el-option label="服务型" value="3" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="企业性质" prop="companyNature">
            <el-select v-model="formData.companyNature" placeholder="请选择" class="!w-full">
              <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="registeredCapital">
            <el-input-number v-model="formData.registeredCapital" :min="0" :precision="2" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="营业执照编号" prop="businessLicenseNo">
            <el-input v-model="formData.businessLicenseNo" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="税号" prop="taxNo">
            <el-input v-model="formData.taxNo" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="联系人" prop="contactName">
            <el-input v-model="formData.contactName" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="联系电话" prop="contactMobile">
            <el-input v-model="formData.contactMobile" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="邮箱" prop="contactEmail">
            <el-input v-model="formData.contactEmail" placeholder="请输入" />
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="公司地址" prop="address">
            <el-input v-model="formData.address" placeholder="请输入地址" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
    </template>
  </el-dialog>
  <!-- å®¡æ ¸å¯¹è¯æ¡† -->
  <el-dialog :title="'审核'" v-model="reviewVisible" width="500px">
    <el-form :model="reviewData" label-width="100px">
      <el-form-item label="审核意见">
        <el-input v-model="reviewData.opinion" type="textarea" :rows="3" placeholder="请输入审核意见" />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="reviewVisible = false">取消</el-button>
      <el-button type="danger" @click="handleReject" v-if="reviewData.type !== 'finance'">拒绝</el-button>
      <el-button type="primary" @click="handleApprove">通过</el-button>
    </template>
  </el-dialog>
  <!-- è¯¦æƒ…对话框 -->
  <el-dialog :title="'申请详情'" v-model="detailVisible" width="750px">
    <el-descriptions :column="2" border>
      <el-descriptions-item label="申请编号">{{ detailData.applyNo }}</el-descriptions-item>
      <el-descriptions-item label="供应商名称">{{ detailData.supplierName }}</el-descriptions-item>
      <el-descriptions-item label="供应商类型">{{ detailData.supplierType }}</el-descriptions-item>
      <el-descriptions-item label="企业性质">{{ detailData.companyNature }}</el-descriptions-item>
      <el-descriptions-item label="营业执照编号">{{ detailData.businessLicenseNo }}</el-descriptions-item>
      <el-descriptions-item label="税号">{{ detailData.taxNo }}</el-descriptions-item>
      <el-descriptions-item label="联系人">{{ detailData.contactName }}</el-descriptions-item>
      <el-descriptions-item label="联系电话">{{ detailData.contactMobile }}</el-descriptions-item>
      <el-descriptions-item label="采购审核人">{{ detailData.purchaseReviewerId }}</el-descriptions-item>
      <el-descriptions-item label="采购审核意见">{{ detailData.purchaseReviewOpinion || '-' }}</el-descriptions-item>
      <el-descriptions-item label="质量审核意见">{{ detailData.qualityReviewOpinion || '-' }}</el-descriptions-item>
      <el-descriptions-item label="财务审核意见">{{ detailData.financeReviewOpinion || '-' }}</el-descriptions-item>
    </el-descriptions>
  </el-dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import { SrmSupplierApplyApi, SrmSupplierApplyVO } from '@/api/srm/supplierApply'
defineOptions({ name: 'SrmSupplierApply' })
const message = useMessage()
const loading = ref(true)
const list = ref<SrmSupplierApplyVO[]>([])
const total = ref(0)
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  applyNo: undefined,
  supplierName: undefined,
  applyStatus: undefined
})
const queryFormRef = ref()
const getList = async () => {
  loading.value = true
  try {
    const data = await SrmSupplierApplyApi.getApplyPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
const handleQuery = () => { queryParams.pageNo = 1; getList() }
const resetQuery = () => { queryFormRef.value.resetFields(); handleQuery() }
// æ–°å¢ž/编辑
const dialogVisible = ref(false)
const formLoading = ref(false)
const formData = ref<any>({})
const formRules = reactive({ supplierName: [{ required: true, message: '供应商名称不能为空', trigger: 'blur' }] })
const formRef = ref()
const openForm = async (id?: number) => {
  if (id) {
    formData.value = await SrmSupplierApplyApi.getApply(id)
  } else {
    formData.value = {}
  }
  dialogVisible.value = true
}
const resetForm = () => { formData.value = {}; formRef.value?.resetFields() }
const submitForm = async () => {
  await formRef.value.validate()
  formLoading.value = true
  try {
    if (formData.value.id) {
      await SrmSupplierApplyApi.updateApply(formData.value)
    } else {
      await SrmSupplierApplyApi.createApply(formData.value)
    }
    message.success('操作成功')
    dialogVisible.value = false
    await getList()
  } finally {
    formLoading.value = false
  }
}
const handleDelete = async (id: number) => {
  try {
    await message.delConfirm()
    await SrmSupplierApplyApi.deleteApply(id)
    message.success('删除成功')
    await getList()
  } catch {}
}
// æäº¤
const handleSubmit = async (id: number) => {
  try {
    await message.confirm('确认提交该申请?')
    await SrmSupplierApplyApi.submitApply(id)
    message.success('提交成功')
    await getList()
  } catch {}
}
// å®¡æ ¸
const reviewVisible = ref(false)
const reviewData = ref({ id: 0, opinion: '', type: '' })
const openReview = (id: number, type: string) => {
  reviewData.value = { id, opinion: '', type }
  reviewVisible.value = true
}
const handleApprove = async () => {
  const apiMap: any = {
    purchase: SrmSupplierApplyApi.reviewPurchase,
    quality: SrmSupplierApplyApi.reviewQuality,
    finance: SrmSupplierApplyApi.reviewFinance
  }
  await apiMap[reviewData.value.type]({ id: reviewData.value.id, opinion: reviewData.value.opinion || '审核通过' })
  message.success('审核通过')
  reviewVisible.value = false
  await getList()
}
const handleReject = async () => {
  await SrmSupplierApplyApi.reviewFinance({ id: reviewData.value.id, opinion: reviewData.value.opinion || '审核拒绝' })
  message.success('已拒绝')
  reviewVisible.value = false
  await getList()
}
// è¯¦æƒ…
const detailVisible = ref(false)
const detailData = ref<any>({})
const openDetail = async (id: number) => {
  detailData.value = await SrmSupplierApplyApi.getApply(id)
  detailVisible.value = true
}
onMounted(() => { getList() })
</script>
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplierEvaluation/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,246 @@
<template>
  <ContentWrap>
    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="88px">
      <el-form-item label="供应商" prop="supplierId">
        <el-select v-model="queryParams.supplierId" placeholder="请选择" clearable class="!w-200px" filterable>
          <el-option v-for="s in supplierList" :key="s.id" :label="s.name" :value="s.id" />
        </el-select>
      </el-form-item>
      <el-form-item label="评价周期" prop="evaluationPeriod">
        <el-input v-model="queryParams.evaluationPeriod" placeholder="如:2026-Q1" clearable class="!w-160px" />
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> æœç´¢</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> é‡ç½®</el-button>
        <el-button type="primary" plain @click="openForm()" v-hasPermi="['srm:supplier-evaluation:create']">
          <Icon icon="ep:plus" class="mr-5px" /> æ–°å¢žè¯„ä»·
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <ContentWrap>
    <el-table v-loading="loading" :data="list" stripe>
      <el-table-column label="供应商ID" align="center" prop="supplierId" min-width="100" />
      <el-table-column label="评价周期" align="center" prop="evaluationPeriod" min-width="120" />
      <el-table-column label="质量评分" align="center" prop="qualityScore" min-width="100" />
      <el-table-column label="合格率(%)" align="center" prop="qualityPassRate" min-width="100" />
      <el-table-column label="不良次数" align="center" prop="qualityDefectCount" min-width="90" />
      <el-table-column label="交付评分" align="center" prop="deliveryScore" min-width="100" />
      <el-table-column label="准时率(%)" align="center" prop="deliveryOnTimeRate" min-width="100" />
      <el-table-column label="延迟次数" align="center" prop="deliveryDelayCount" min-width="90" />
      <el-table-column label="价格评分" align="center" prop="priceScore" min-width="100" />
      <el-table-column label="服务评分" align="center" prop="serviceScore" min-width="100" />
      <el-table-column label="综合评分" align="center" prop="compositeScore" min-width="100">
        <template #default="scope">
          <el-tag v-if="scope.row.evaluationLevel === 'A'" type="success">{{ scope.row.compositeScore }}</el-tag>
          <el-tag v-else-if="scope.row.evaluationLevel === 'B'" type="primary">{{ scope.row.compositeScore }}</el-tag>
          <el-tag v-else-if="scope.row.evaluationLevel === 'C'" type="warning">{{ scope.row.compositeScore }}</el-tag>
          <el-tag v-else type="danger">{{ scope.row.compositeScore }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="等级" align="center" prop="evaluationLevel" min-width="80">
        <template #default="scope">
          <el-tag v-if="scope.row.evaluationLevel === 'A'" type="success">A</el-tag>
          <el-tag v-else-if="scope.row.evaluationLevel === 'B'" type="primary">B</el-tag>
          <el-tag v-else-if="scope.row.evaluationLevel === 'C'" type="warning">C</el-tag>
          <el-tag v-else type="danger">D</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="评价时间" align="center" prop="evaluationTime" :formatter="dateFormatter" width="160px" />
      <el-table-column label="操作" align="center" width="160" fixed="right">
        <template #default="scope">
          <el-button link type="primary" @click="openDetail(scope.row.id)" v-hasPermi="['srm:supplier-evaluation:query']">详情</el-button>
          <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['srm:supplier-evaluation:delete']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
  </ContentWrap>
  <!-- æ–°å¢ž/编辑对话框 -->
  <el-dialog :title="'新增评价'" v-model="dialogVisible" width="650px" @close="resetForm">
    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="供应商" prop="supplierId">
            <el-select v-model="formData.supplierId" placeholder="请选择" class="!w-full" filterable>
              <el-option v-for="s in supplierList" :key="s.id" :label="s.name" :value="s.id" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="评价周期" prop="evaluationPeriod">
            <el-input v-model="formData.evaluationPeriod" placeholder="如:2026-Q1" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="质量评分" prop="qualityScore">
            <el-input-number v-model="formData.qualityScore" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="合格率(%)" prop="qualityPassRate">
            <el-input-number v-model="formData.qualityPassRate" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="不良次数" prop="qualityDefectCount">
            <el-input-number v-model="formData.qualityDefectCount" :min="0" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="交付评分" prop="deliveryScore">
            <el-input-number v-model="formData.deliveryScore" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="准时率(%)" prop="deliveryOnTimeRate">
            <el-input-number v-model="formData.deliveryOnTimeRate" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="延迟次数" prop="deliveryDelayCount">
            <el-input-number v-model="formData.deliveryDelayCount" :min="0" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="价格评分" prop="priceScore">
            <el-input-number v-model="formData.priceScore" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="服务评分" prop="serviceScore">
            <el-input-number v-model="formData.serviceScore" :min="0" :max="100" :precision="1" controls-position="right" class="!w-full" />
          </el-form-item>
        </el-col>
        <el-col :span="24">
          <el-form-item label="备注" prop="remark">
            <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
    </template>
  </el-dialog>
  <!-- è¯¦æƒ…对话框 -->
  <el-dialog :title="'评价详情'" v-model="detailVisible" width="650px">
    <el-descriptions :column="2" border>
      <el-descriptions-item label="评价周期">{{ detailData.evaluationPeriod }}</el-descriptions-item>
      <el-descriptions-item label="评价等级">
        <el-tag v-if="detailData.evaluationLevel === 'A'" type="success">A</el-tag>
        <el-tag v-else-if="detailData.evaluationLevel === 'B'" type="primary">B</el-tag>
        <el-tag v-else-if="detailData.evaluationLevel === 'C'" type="warning">C</el-tag>
        <el-tag v-else type="danger">D</el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="质量评分">{{ detailData.qualityScore }}</el-descriptions-item>
      <el-descriptions-item label="合格率">{{ detailData.qualityPassRate }}%</el-descriptions-item>
      <el-descriptions-item label="不良次数">{{ detailData.qualityDefectCount }}</el-descriptions-item>
      <el-descriptions-item label="交付评分">{{ detailData.deliveryScore }}</el-descriptions-item>
      <el-descriptions-item label="准时率">{{ detailData.deliveryOnTimeRate }}%</el-descriptions-item>
      <el-descriptions-item label="延迟次数">{{ detailData.deliveryDelayCount }}</el-descriptions-item>
      <el-descriptions-item label="价格评分">{{ detailData.priceScore }}</el-descriptions-item>
      <el-descriptions-item label="服务评分">{{ detailData.serviceScore }}</el-descriptions-item>
      <el-descriptions-item label="综合评分">{{ detailData.compositeScore }}</el-descriptions-item>
      <el-descriptions-item label="评价时间">{{ detailData.evaluationTime }}</el-descriptions-item>
      <el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
    </el-descriptions>
  </el-dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import { SrmSupplierEvaluationApi, SrmSupplierEvaluationVO } from '@/api/srm/supplierEvaluation'
import { SrmSupplierApi } from '@/api/srm/supplier'
defineOptions({ name: 'SrmSupplierEvaluation' })
const message = useMessage()
const loading = ref(true)
const list = ref<SrmSupplierEvaluationVO[]>([])
const total = ref(0)
const supplierList = ref<any[]>([])
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  supplierId: undefined,
  evaluationPeriod: undefined
})
const queryFormRef = ref()
const getList = async () => {
  loading.value = true
  try {
    const data = await SrmSupplierEvaluationApi.getEvaluationPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
const handleQuery = () => { queryParams.pageNo = 1; getList() }
const resetQuery = () => { queryFormRef.value.resetFields(); handleQuery() }
// æ–°å¢ž/编辑
const dialogVisible = ref(false)
const formLoading = ref(false)
const formData = ref<any>({})
const formRules = reactive({
  supplierId: [{ required: true, message: '供应商不能为空', trigger: 'change' }],
  evaluationPeriod: [{ required: true, message: '评价周期不能为空', trigger: 'blur' }]
})
const formRef = ref()
const openForm = async () => {
  formData.value = { qualityDefectCount: 0, deliveryDelayCount: 0 }
  dialogVisible.value = true
}
const resetForm = () => { formData.value = {}; formRef.value?.resetFields() }
const submitForm = async () => {
  await formRef.value.validate()
  formLoading.value = true
  try {
    await SrmSupplierEvaluationApi.createEvaluation(formData.value)
    message.success('创建成功')
    dialogVisible.value = false
    await getList()
  } finally {
    formLoading.value = false
  }
}
const handleDelete = async (id: number) => {
  try {
    await message.delConfirm()
    await SrmSupplierEvaluationApi.deleteEvaluation(id)
    message.success('删除成功')
    await getList()
  } catch {}
}
// è¯¦æƒ…
const detailVisible = ref(false)
const detailData = ref<any>({})
const openDetail = async (id: number) => {
  detailData.value = await SrmSupplierEvaluationApi.getEvaluation(id)
  detailVisible.value = true
}
const loadSuppliers = async () => {
  try {
    const data = await SrmSupplierApi.getSupplierSimpleList()
    supplierList.value = data || []
  } catch {}
}
onMounted(() => { getList(); loadSuppliers() })
</script>