Java全栈项目-企业销售管理系统

项目概述

企业销售管理系统是一个基于Java全栈技术开发的综合性管理平台,旨在帮助企业高效管理销售流程、客户关系和业务数据。本文将详细介绍该系统的技术架构、核心功能以及实现要点。

技术栈

后端技术

  • Spring Boot 2.7.x:核心框架
  • Spring Security:认证和授权
  • MyBatis Plus:ORM框架
  • MySQL 8.0:数据库
  • Redis:缓存处理
  • JWT:用户令牌管理
  • Maven:项目管理工具

前端技术

  • Vue 3:前端框架
  • Element Plus:UI组件库
  • Axios:HTTP客户端
  • Vuex:状态管理
  • Vue Router:路由管理
  • ECharts:数据可视化

核心功能模块

1. 用户权限管理

  • 基于RBAC的权限控制
  • 多角色管理
  • 动态权限分配
  • 操作日志记录

2. 客户管理

  • 客户信息维护
  • 客户分类管理
  • 客户跟进记录
  • 客户画像分析

3. 销售管理

  • 销售订单处理
  • 报价管理
  • 合同管理
  • 发票管理

4. 产品管理

  • 产品信息维护
  • 产品分类管理
  • 价格策略管理
  • 库存管理

5. 数据分析

  • 销售业绩统计
  • 客户转化率分析
  • 产品销售趋势
  • 实时数据大屏

技术实现要点

1. 后端架构

@SpringBootApplication
public class SalesManagementApplication {
    public static void main(String[] args) {
        SpringApplication.run(SalesManagementApplication.class, args);
    }
}

2. 权限控制

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter());
    }
}

3. 数据访问层

@Mapper
public interface SalesOrderMapper extends BaseMapper<SalesOrder> {
    @Select("SELECT * FROM sales_order WHERE customer_id = #{customerId}")
    List<SalesOrder> findByCustomerId(Long customerId);
}

部署架构

1. 开发环境

  • JDK 11+
  • Node.js 16+
  • MySQL 8.0
  • Redis 6.x

2. 服务器配置

  • 应用服务器:2核4G
  • 数据库服务器:4核8G
  • Redis服务器:2核4G

3. 部署流程

  1. 后端服务打包:mvn clean package
  2. 前端构建:npm run build
  3. Docker容器化部署
  4. Nginx反向代理配置

性能优化

1. 数据库优化

  • 索引优化
  • SQL语句优化
  • 分库分表设计

2. 缓存策略

  • Redis缓存热点数据
  • 本地缓存使用
  • 缓存更新策略

3. JVM调优

  • 内存分配优化
  • GC策略调整
  • 线程池配置

安全性考虑

1. 接口安全

  • 参数验证
  • SQL注入防护
  • XSS防护
  • CSRF防护

2. 数据安全

  • 敏感数据加密
  • 数据备份策略
  • 访问权限控制

项目亮点

  1. 采用前后端分离架构,提高开发效率和维护性
  2. 使用Redis实现分布式Session,支持横向扩展
  3. 实现基于AOP的操作日志记录
  4. 集成ELK实现统一日志管理
  5. 使用Docker实现容器化部署

总结与展望

本项目采用主流的Java全栈技术栈,实现了一个功能完整、性能优异的企业级销售管理系统。通过合理的技术选型和架构设计,系统具有良好的可扩展性和维护性。未来将继续优化系统性能,增加更多智能化功能,如AI辅助决策、智能报表等。

Directory Content Summary

Source Directory: ./enterprise-sales-system

Directory Structure

enterprise-sales-system/
  README.md
  backend/
    pom.xml
    src/
      main/
        java/
          com/
            example/
              sales/
                aspect/
                  LogAspect.java
                config/
                  SecurityConfig.java
                controller/
                  CustomerCategoryController.java
                  CustomerController.java
                  CustomerTagController.java
                  DataAnalysisController.java
                  InvoiceController.java
                  ProductCategoryController.java
                  ProductController.java
                  ProductInventoryController.java
                  ProductPriceStrategyController.java
                  QuotationController.java
                  SalesContractController.java
                  SalesOrderController.java
                  WarehouseController.java
                entity/
                  Customer.java
                  CustomerCategory.java
                  CustomerConversion.java
                  CustomerFollowRecord.java
                  CustomerTag.java
                  InventoryTransaction.java
                  Invoice.java
                  InvoiceItem.java
                  Product.java
                  ProductCategory.java
                  ProductInventory.java
                  ProductPriceStrategy.java
                  ProductSalesTrend.java
                  Quotation.java
                  QuotationItem.java
                  RealtimeMetrics.java
                  RegionalSales.java
                  SalesContract.java
                  SalesFunnel.java
                  SalesOrder.java
                  SalesOrderItem.java
                  SalesPerformance.java
                  SysPermission.java
                  SysRole.java
                  SysUser.java
                  Warehouse.java
                security/
                  JwtAuthenticationFilter.java
                  JwtTokenProvider.java
                service/
                  CustomerCategoryService.java
                  CustomerTagService.java
                  DataAnalysisService.java
                  InvoiceService.java
                  QuotationService.java
                  SalesContractService.java
                  SalesOrderService.java
                  impl/
                    CustomerCategoryServiceImpl.java
                    CustomerServiceImpl.java
                    CustomerTagServiceImpl.java
                    DataAnalysisServiceImpl.java
                    InvoiceServiceImpl.java
                    QuotationServiceImpl.java
                    SalesContractServiceImpl.java
                    SalesOrderServiceImpl.java
                vo/
                  ContractQueryVO.java
                  CustomerAnalysisVO.java
                  CustomerQueryVO.java
                  InvoiceQueryVO.java
                  ProductCategoryQueryVO.java
                  ProductInventoryQueryVO.java
                  ProductPriceStrategyQueryVO.java
                  ProductQueryVO.java
                  QuotationQueryVO.java
                  SalesOrderQueryVO.java
        resources/
          application.yml
    target/
      classes/
        application.yml
        com/
          example/
            sales/
              aspect/
              config/
              entity/
              security/
      generated-sources/
        annotations/
      generated-test-sources/
        test-annotations/
      test-classes/
  frontend/
    package.json
    vite.config.js
    src/
      api/
        analysis/
          index.js
        customer/
          category.js
          index.js
          tag.js
        product/
          category.js
          index.js
          inventory.js
          price.js
          inventory/
            transaction.js
        sales/
          contract.js
          invoice.js
          order.js
          quotation.js
        system/
          user.js
        warehouse/
          index.js
      router/
        modules/
          analysis.js
          product.js
      views/
        analysis/
          index.vue
        customer/
          analysis.vue
          index.vue
          category/
            index.vue
          components/
            CustomerForm.vue
            FollowRecordDialog.vue
          tag/
            index.vue
        dashboard/
          index.vue
        product/
          index.vue
          category/
            index.vue
          components/
            InventoryList.vue
            PriceStrategyList.vue
            ProductForm.vue
        sales/
          contract/
            index.vue
            components/
              ContractDetail.vue
              ContractForm.vue
          invoice/
            index.vue
            components/
              InvoiceDetail.vue
              InvoiceForm.vue
          order/
            index.vue
            components/
              OrderDetail.vue
              OrderForm.vue
          quotation/
            index.vue
            components/
              QuotationDetail.vue
              QuotationForm.vue
        system/
          user/
            index.vue
        warehouse/
          index.vue
  sql/
    analysis_tables.sql
    customer_tables.sql
    init.sql
    product_tables.sql
    rbac_tables.sql
    sales_tables.sql

File Contents

backend\pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>enterprise-sales-system</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <druid.version>1.2.16</druid.version>
        <knife4j.version>4.1.0</knife4j.version>
        <jjwt.version>0.11.5</jjwt.version>
        <hutool.version>5.8.20</hutool.version>
        <easyexcel.version>3.3.2</easyexcel.version>
    </properties>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- Knife4j API文档 -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- EasyExcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>${easyexcel.version}</version>
        </dependency>

        <!-- Hutool工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

backend\src\main\java\com\example\sales\aspect\LogAspect.java

package com.example.sales.aspect;

import com.example.sales.entity.SysOperationLog;
import com.example.sales.service.SysOperationLogService;
import com.example.sales.util.SecurityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private SysOperationLogService operationLogService;

    @Autowired
    private ObjectMapper objectMapper;

    @Pointcut("@annotation(com.example.sales.annotation.Log)")
    public void logPointCut() {}

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = null;
        Exception exception = null;

        try {
            result = point.proceed();
            return result;
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            saveOperationLog(point, result, exception, System.currentTimeMillis() - beginTime);
        }
    }

    private void saveOperationLog(ProceedingJoinPoint joinPoint, Object result, Exception e, long time) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            SysOperationLog operationLog = new SysOperationLog();

            // 设置操作用户信息
            String username = SecurityUtils.getCurrentUsername();
            operationLog.setUserId(SecurityUtils.getCurrentUserId());
            operationLog.setUsername(username);

            // 设置请求信息
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                operationLog.setIp(request.getRemoteAddr());
                operationLog.setMethod(request.getMethod() + " " + request.getRequestURI());
            }

            // 设置请求参数
            String params = objectMapper.writeValueAsString(joinPoint.getArgs());
            operationLog.setParams(params);

            // 设置操作状态
            operationLog.setStatus(e == null ? 1 : 0);
            if (e != null) {
                operationLog.setErrorMsg(e.getMessage());
            }

            operationLog.setOperationTime(LocalDateTime.now());

            // 保存日志
            operationLogService.save(operationLog);
        } catch (Exception ex) {
            log.error("Save operation log failed", ex);
        }
    }
}

backend\src\main\java\com\example\sales\config\SecurityConfig.java

package com.example.sales.config;

import com.example.sales.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors()
                .and()
            .csrf()
                .disable()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers("/api/auth/**")
                    .permitAll()
                .antMatchers("/api/test/**")
                    .permitAll()
                .anyRequest()
                    .authenticated();

        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

backend\src\main\java\com\example\sales\controller\CustomerCategoryController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.CustomerCategory;
import com.example.sales.service.CustomerCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/customer/category")
public class CustomerCategoryController {
    
    @Autowired
    private CustomerCategoryService customerCategoryService;

    @GetMapping("/list")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<List<CustomerCategory>> list() {
        return Result.success(customerCategoryService.list());
    }

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<IPage<CustomerCategory>> page(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) String keyword) {
        return Result.success(customerCategoryService.getPage(pageNum, pageSize, keyword));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('customer:add')")
    public Result<Boolean> add(@RequestBody CustomerCategory category) {
        return Result.success(customerCategoryService.save(category));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('customer:edit')")
    public Result<Boolean> update(@RequestBody CustomerCategory category) {
        return Result.success(customerCategoryService.updateById(category));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('customer:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(customerCategoryService.removeById(id));
    }
}

backend\src\main\java\com\example\sales\controller\CustomerController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Customer;
import com.example.sales.entity.CustomerFollowRecord;
import com.example.sales.service.CustomerService;
import com.example.sales.vo.CustomerAnalysisVO;
import com.example.sales.vo.CustomerQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/customer")
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @GetMapping("/list")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<IPage<Customer>> list(CustomerQueryVO queryVO) {
        return Result.success(customerService.getCustomerPage(queryVO));
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<Customer> getById(@PathVariable Long id) {
        return Result.success(customerService.getCustomerById(id));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('customer:add')")
    public Result<Boolean> add(@RequestBody Customer customer) {
        return Result.success(customerService.saveCustomer(customer));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('customer:edit')")
    public Result<Boolean> update(@RequestBody Customer customer) {
        return Result.success(customerService.updateCustomer(customer));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('customer:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(customerService.removeCustomer(id));
    }

    @PostMapping("/follow")
    @PreAuthorize("hasAuthority('customer:follow')")
    public Result<Boolean> addFollowRecord(@RequestBody CustomerFollowRecord record) {
        return Result.success(customerService.addFollowRecord(record));
    }

    @GetMapping("/follow/{customerId}")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<List<CustomerFollowRecord>> getFollowRecords(@PathVariable Long customerId) {
        return Result.success(customerService.getFollowRecords(customerId));
    }

    @GetMapping("/analysis/overview")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<CustomerAnalysisVO> getAnalysisOverview() {
        return Result.success(customerService.getCustomerAnalysis());
    }

    @GetMapping("/analysis/distribution")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<Map<String, Object>> getDistributionData() {
        return Result.success(customerService.getCustomerDistribution());
    }

    @GetMapping("/analysis/trend")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<Map<String, Object>> getTrendData() {
        return Result.success(customerService.getCustomerTrend());
    }
}

backend\src\main\java\com\example\sales\controller\CustomerTagController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.CustomerTag;
import com.example.sales.service.CustomerTagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/customer/tag")
public class CustomerTagController {
    
    @Autowired
    private CustomerTagService customerTagService;

    @GetMapping("/list")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<List<CustomerTag>> list() {
        return Result.success(customerTagService.list());
    }

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('customer:view')")
    public Result<IPage<CustomerTag>> page(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) String keyword) {
        return Result.success(customerTagService.getPage(pageNum, pageSize, keyword));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('customer:add')")
    public Result<Boolean> add(@RequestBody CustomerTag tag) {
        return Result.success(customerTagService.save(tag));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('customer:edit')")
    public Result<Boolean> update(@RequestBody CustomerTag tag) {
        return Result.success(customerTagService.updateById(tag));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('customer:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(customerTagService.removeById(id));
    }
}

backend\src\main\java\com\example\sales\controller\DataAnalysisController.java

package com.example.sales.controller;

import com.example.sales.common.api.CommonResult;
import com.example.sales.service.DataAnalysisService;
import com.example.sales.vo.analysis.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/analysis")
public class DataAnalysisController {
    
    @Autowired
    private DataAnalysisService dataAnalysisService;
    
    @GetMapping("/performance")
    public CommonResult<List<SalesPerformanceVO>> getSalesPerformance(
            @RequestParam Integer year,
            @RequestParam(required = false) Integer month,
            @RequestParam(required = false) Long salesUserId) {
        return CommonResult.success(dataAnalysisService.getSalesPerformance(year, month, salesUserId));
    }
    
    @GetMapping("/conversion")
    public CommonResult<List<CustomerConversionVO>> getCustomerConversion(
            @RequestParam Integer year,
            @RequestParam(required = false) Integer month) {
        return CommonResult.success(dataAnalysisService.getCustomerConversion(year, month));
    }
    
    @GetMapping("/product/trend")
    public CommonResult<List<ProductSalesTrendVO>> getProductSalesTrend(
            @RequestParam Integer year,
            @RequestParam(required = false) Integer month,
            @RequestParam(required = false) Long productId,
            @RequestParam(required = false) Long categoryId) {
        return CommonResult.success(dataAnalysisService.getProductSalesTrend(year, month, productId, categoryId));
    }
    
    @GetMapping("/realtime/overview")
    public CommonResult<Map<String, Object>> getRealtimeOverview() {
        return CommonResult.success(dataAnalysisService.getRealtimeOverview());
    }
    
    @GetMapping("/realtime/trend")
    public CommonResult<Map<String, Object>> getRealtimeTrend(
            @RequestParam String metricCode,
            @RequestParam Integer hours) {
        return CommonResult.success(dataAnalysisService.getRealtimeTrend(metricCode, hours));
    }
    
    @GetMapping("/funnel")
    public CommonResult<List<SalesFunnelVO>> getSalesFunnel(
            @RequestParam Integer year,
            @RequestParam(required = false) Integer month) {
        return CommonResult.success(dataAnalysisService.getSalesFunnel(year, month));
    }
    
    @GetMapping("/regional")
    public CommonResult<List<RegionalSalesVO>> getRegionalSales(
            @RequestParam Integer year,
            @RequestParam(required = false) Integer month) {
        return CommonResult.success(dataAnalysisService.getRegionalSales(year, month));
    }
    
    @GetMapping("/dashboard")
    public CommonResult<Map<String, Object>> getDashboardData() {
        return CommonResult.success(dataAnalysisService.getDashboardData());
    }
}

backend\src\main\java\com\example\sales\controller\InvoiceController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Invoice;
import com.example.sales.service.InvoiceService;
import com.example.sales.vo.InvoiceQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/sales/invoice")
public class InvoiceController {
    
    @Autowired
    private InvoiceService invoiceService;

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('sales:invoice:view')")
    public Result<IPage<Invoice>> page(InvoiceQueryVO queryVO) {
        return Result.success(invoiceService.getPage(queryVO));
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:invoice:view')")
    public Result<Invoice> getById(@PathVariable Long id) {
        return Result.success(invoiceService.getInvoiceById(id));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('sales:invoice:add')")
    public Result<Boolean> add(@RequestBody Invoice invoice) {
        return Result.success(invoiceService.saveInvoice(invoice));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('sales:invoice:edit')")
    public Result<Boolean> update(@RequestBody Invoice invoice) {
        return Result.success(invoiceService.updateInvoice(invoice));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:invoice:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(invoiceService.removeById(id));
    }

    @PostMapping("/{id}/submit")
    @PreAuthorize("hasAuthority('sales:invoice:submit')")
    public Result<Boolean> submit(@PathVariable Long id) {
        return Result.success(invoiceService.submitInvoice(id));
    }

    @PostMapping("/{id}/issue")
    @PreAuthorize("hasAuthority('sales:invoice:issue')")
    public Result<Boolean> issue(@PathVariable Long id) {
        return Result.success(invoiceService.issueInvoice(id));
    }

    @PostMapping("/{id}/mail")
    @PreAuthorize("hasAuthority('sales:invoice:mail')")
    public Result<Boolean> mail(@PathVariable Long id) {
        return Result.success(invoiceService.mailInvoice(id));
    }

    @PostMapping("/{id}/void")
    @PreAuthorize("hasAuthority('sales:invoice:void')")
    public Result<Boolean> voidInvoice(@PathVariable Long id, @RequestParam String reason) {
        return Result.success(invoiceService.voidInvoice(id, reason));
    }
}

backend\src\main\java\com\example\sales\controller\ProductCategoryController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductCategory;
import com.example.sales.service.ProductCategoryService;
import com.example.sales.vo.ProductCategoryQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/product/category")
public class ProductCategoryController {
    
    @Autowired
    private ProductCategoryService productCategoryService;
    
    @GetMapping("/list")
    public CommonResult<List<ProductCategory>> list(ProductCategoryQueryVO queryVO) {
        return CommonResult.success(productCategoryService.list(queryVO));
    }
    
    @GetMapping("/tree")
    public CommonResult<List<ProductCategory>> tree() {
        return CommonResult.success(productCategoryService.tree());
    }
    
    @GetMapping("/{id}")
    public CommonResult<ProductCategory> getById(@PathVariable Long id) {
        return CommonResult.success(productCategoryService.getById(id));
    }
    
    @PostMapping
    public CommonResult<Boolean> add(@RequestBody ProductCategory category) {
        return CommonResult.success(productCategoryService.save(category));
    }
    
    @PutMapping
    public CommonResult<Boolean> update(@RequestBody ProductCategory category) {
        return CommonResult.success(productCategoryService.updateById(category));
    }
    
    @DeleteMapping("/{id}")
    public CommonResult<Boolean> delete(@PathVariable Long id) {
        return CommonResult.success(productCategoryService.removeById(id));
    }
    
    @PutMapping("/{id}/status")
    public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
        return CommonResult.success(productCategoryService.updateStatus(id, status));
    }
}

backend\src\main\java\com\example\sales\controller\ProductController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.Product;
import com.example.sales.service.ProductService;
import com.example.sales.vo.ProductQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/product")
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @GetMapping("/page")
    public CommonResult<IPage<Product>> page(ProductQueryVO queryVO,
                                           @RequestParam(defaultValue = "1") Integer pageNum,
                                           @RequestParam(defaultValue = "10") Integer pageSize) {
        return CommonResult.success(productService.page(queryVO, pageNum, pageSize));
    }
    
    @GetMapping("/{id}")
    public CommonResult<Product> getById(@PathVariable Long id) {
        return CommonResult.success(productService.getById(id));
    }
    
    @PostMapping
    public CommonResult<Boolean> add(@RequestBody Product product) {
        return CommonResult.success(productService.save(product));
    }
    
    @PutMapping
    public CommonResult<Boolean> update(@RequestBody Product product) {
        return CommonResult.success(productService.updateById(product));
    }
    
    @DeleteMapping("/{id}")
    public CommonResult<Boolean> delete(@PathVariable Long id) {
        return CommonResult.success(productService.removeById(id));
    }
    
    @PutMapping("/{id}/status")
    public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
        return CommonResult.success(productService.updateStatus(id, status));
    }
    
    @GetMapping("/code/generate")
    public CommonResult<String> generateCode() {
        return CommonResult.success(productService.generateCode());
    }
}

backend\src\main\java\com\example\sales\controller\ProductInventoryController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductInventory;
import com.example.sales.entity.InventoryTransaction;
import com.example.sales.service.ProductInventoryService;
import com.example.sales.vo.ProductInventoryQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/product/inventory")
public class ProductInventoryController {
    
    @Autowired
    private ProductInventoryService inventoryService;
    
    @GetMapping("/page")
    public CommonResult<IPage<ProductInventory>> page(ProductInventoryQueryVO queryVO,
                                                    @RequestParam(defaultValue = "1") Integer pageNum,
                                                    @RequestParam(defaultValue = "10") Integer pageSize) {
        return CommonResult.success(inventoryService.page(queryVO, pageNum, pageSize));
    }
    
    @GetMapping("/{id}")
    public CommonResult<ProductInventory> getById(@PathVariable Long id) {
        return CommonResult.success(inventoryService.getById(id));
    }
    
    @PostMapping("/in")
    public CommonResult<Boolean> stockIn(@RequestBody InventoryTransaction transaction) {
        return CommonResult.success(inventoryService.stockIn(transaction));
    }
    
    @PostMapping("/out")
    public CommonResult<Boolean> stockOut(@RequestBody InventoryTransaction transaction) {
        return CommonResult.success(inventoryService.stockOut(transaction));
    }
    
    @PostMapping("/lock")
    public CommonResult<Boolean> lockStock(@RequestBody InventoryTransaction transaction) {
        return CommonResult.success(inventoryService.lockStock(transaction));
    }
    
    @PostMapping("/unlock")
    public CommonResult<Boolean> unlockStock(@RequestBody InventoryTransaction transaction) {
        return CommonResult.success(inventoryService.unlockStock(transaction));
    }
    
    @GetMapping("/alert")
    public CommonResult<List<ProductInventory>> listAlertItems() {
        return CommonResult.success(inventoryService.listAlertItems());
    }
    
    @GetMapping("/transaction/page")
    public CommonResult<IPage<InventoryTransaction>> transactionPage(
            @RequestParam(required = false) Long productId,
            @RequestParam(required = false) String warehouseCode,
            @RequestParam(required = false) String transactionType,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return CommonResult.success(inventoryService.transactionPage(productId, warehouseCode, transactionType, pageNum, pageSize));
    }
    
    @PutMapping("/threshold")
    public CommonResult<Boolean> updateAlertThreshold(@RequestParam Long id, @RequestParam Integer threshold) {
        return CommonResult.success(inventoryService.updateAlertThreshold(id, threshold));
    }
}

backend\src\main\java\com\example\sales\controller\ProductPriceStrategyController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.ProductPriceStrategy;
import com.example.sales.service.ProductPriceStrategyService;
import com.example.sales.vo.ProductPriceStrategyQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;

@RestController
@RequestMapping("/product/price-strategy")
public class ProductPriceStrategyController {
    
    @Autowired
    private ProductPriceStrategyService priceStrategyService;
    
    @GetMapping("/page")
    public CommonResult<IPage<ProductPriceStrategy>> page(ProductPriceStrategyQueryVO queryVO,
                                                        @RequestParam(defaultValue = "1") Integer pageNum,
                                                        @RequestParam(defaultValue = "10") Integer pageSize) {
        return CommonResult.success(priceStrategyService.page(queryVO, pageNum, pageSize));
    }
    
    @GetMapping("/{id}")
    public CommonResult<ProductPriceStrategy> getById(@PathVariable Long id) {
        return CommonResult.success(priceStrategyService.getById(id));
    }
    
    @PostMapping
    public CommonResult<Boolean> add(@RequestBody ProductPriceStrategy strategy) {
        return CommonResult.success(priceStrategyService.save(strategy));
    }
    
    @PutMapping
    public CommonResult<Boolean> update(@RequestBody ProductPriceStrategy strategy) {
        return CommonResult.success(priceStrategyService.updateById(strategy));
    }
    
    @DeleteMapping("/{id}")
    public CommonResult<Boolean> delete(@PathVariable Long id) {
        return CommonResult.success(priceStrategyService.removeById(id));
    }
    
    @PutMapping("/{id}/status")
    public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
        return CommonResult.success(priceStrategyService.updateStatus(id, status));
    }
    
    @GetMapping("/calculate")
    public CommonResult<BigDecimal> calculatePrice(@RequestParam Long productId,
                                                 @RequestParam(required = false) Long customerCategoryId,
                                                 @RequestParam Integer quantity) {
        return CommonResult.success(priceStrategyService.calculatePrice(productId, customerCategoryId, quantity));
    }
}

backend\src\main\java\com\example\sales\controller\QuotationController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.Quotation;
import com.example.sales.service.QuotationService;
import com.example.sales.vo.QuotationQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/sales/quotation")
public class QuotationController {
    
    @Autowired
    private QuotationService quotationService;

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('sales:quotation:view')")
    public Result<IPage<Quotation>> page(QuotationQueryVO queryVO) {
        return Result.success(quotationService.getPage(queryVO));
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:quotation:view')")
    public Result<Quotation> getById(@PathVariable Long id) {
        return Result.success(quotationService.getQuotationById(id));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('sales:quotation:add')")
    public Result<Boolean> add(@RequestBody Quotation quotation) {
        return Result.success(quotationService.saveQuotation(quotation));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('sales:quotation:edit')")
    public Result<Boolean> update(@RequestBody Quotation quotation) {
        return Result.success(quotationService.updateQuotation(quotation));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:quotation:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(quotationService.removeById(id));
    }

    @PostMapping("/{id}/send")
    @PreAuthorize("hasAuthority('sales:quotation:send')")
    public Result<Boolean> send(@PathVariable Long id) {
        return Result.success(quotationService.sendQuotation(id));
    }

    @PostMapping("/{id}/accept")
    @PreAuthorize("hasAuthority('sales:quotation:process')")
    public Result<Boolean> accept(@PathVariable Long id) {
        return Result.success(quotationService.acceptQuotation(id));
    }

    @PostMapping("/{id}/reject")
    @PreAuthorize("hasAuthority('sales:quotation:process')")
    public Result<Boolean> reject(@PathVariable Long id, @RequestParam String reason) {
        return Result.success(quotationService.rejectQuotation(id, reason));
    }
}

backend\src\main\java\com\example\sales\controller\SalesContractController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.SalesContract;
import com.example.sales.service.SalesContractService;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/sales/contract")
public class SalesContractController {
    
    @Autowired
    private SalesContractService contractService;

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('sales:contract:view')")
    public Result<IPage<SalesContract>> page(ContractQueryVO queryVO) {
        return Result.success(contractService.getPage(queryVO));
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:contract:view')")
    public Result<SalesContract> getById(@PathVariable Long id) {
        return Result.success(contractService.getContractById(id));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('sales:contract:add')")
    public Result<Boolean> add(@RequestBody SalesContract contract) {
        return Result.success(contractService.saveContract(contract));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('sales:contract:edit')")
    public Result<Boolean> update(@RequestBody SalesContract contract) {
        return Result.success(contractService.updateContract(contract));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:contract:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(contractService.removeById(id));
    }

    @PostMapping("/{id}/sign")
    @PreAuthorize("hasAuthority('sales:contract:sign')")
    public Result<Boolean> sign(@PathVariable Long id) {
        return Result.success(contractService.signContract(id));
    }

    @PostMapping("/{id}/terminate")
    @PreAuthorize("hasAuthority('sales:contract:terminate')")
    public Result<Boolean> terminate(@PathVariable Long id, @RequestParam String reason) {
        return Result.success(contractService.terminateContract(id, reason));
    }

    @PostMapping("/upload")
    @PreAuthorize("hasAuthority('sales:contract:upload')")
    public Result<String> uploadAttachment(@RequestParam("file") MultipartFile file) {
        return Result.success(contractService.uploadAttachment(file));
    }
}

backend\src\main\java\com\example\sales\controller\SalesOrderController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.Result;
import com.example.sales.entity.SalesOrder;
import com.example.sales.service.SalesOrderService;
import com.example.sales.vo.SalesOrderQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/sales/order")
public class SalesOrderController {
    
    @Autowired
    private SalesOrderService salesOrderService;

    @GetMapping("/page")
    @PreAuthorize("hasAuthority('sales:order:view')")
    public Result<IPage<SalesOrder>> page(SalesOrderQueryVO queryVO) {
        return Result.success(salesOrderService.getPage(queryVO));
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:order:view')")
    public Result<SalesOrder> getById(@PathVariable Long id) {
        return Result.success(salesOrderService.getOrderById(id));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('sales:order:add')")
    public Result<Boolean> add(@RequestBody SalesOrder order) {
        return Result.success(salesOrderService.saveOrder(order));
    }

    @PutMapping
    @PreAuthorize("hasAuthority('sales:order:edit')")
    public Result<Boolean> update(@RequestBody SalesOrder order) {
        return Result.success(salesOrderService.updateOrder(order));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('sales:order:delete')")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(salesOrderService.removeById(id));
    }

    @PostMapping("/{id}/submit")
    @PreAuthorize("hasAuthority('sales:order:submit')")
    public Result<Boolean> submit(@PathVariable Long id) {
        return Result.success(salesOrderService.submitOrder(id));
    }

    @PostMapping("/{id}/approve")
    @PreAuthorize("hasAuthority('sales:order:approve')")
    public Result<Boolean> approve(@PathVariable Long id) {
        return Result.success(salesOrderService.approveOrder(id));
    }

    @PostMapping("/{id}/reject")
    @PreAuthorize("hasAuthority('sales:order:approve')")
    public Result<Boolean> reject(@PathVariable Long id, @RequestParam String reason) {
        return Result.success(salesOrderService.rejectOrder(id, reason));
    }
}

backend\src\main\java\com\example\sales\controller\WarehouseController.java

package com.example.sales.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.sales.common.api.CommonResult;
import com.example.sales.entity.Warehouse;
import com.example.sales.service.WarehouseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/warehouse")
public class WarehouseController {
    
    @Autowired
    private WarehouseService warehouseService;
    
    @GetMapping("/list")
    public CommonResult<List<Warehouse>> list(@RequestParam(required = false) String status) {
        return CommonResult.success(warehouseService.list(status));
    }
    
    @GetMapping("/page")
    public CommonResult<IPage<Warehouse>> page(
            @RequestParam(required = false) String warehouseCode,
            @RequestParam(required = false) String warehouseName,
            @RequestParam(required = false) String status,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return CommonResult.success(warehouseService.page(warehouseCode, warehouseName, status, pageNum, pageSize));
    }
    
    @GetMapping("/{id}")
    public CommonResult<Warehouse> getById(@PathVariable Long id) {
        return CommonResult.success(warehouseService.getById(id));
    }
    
    @PostMapping
    public CommonResult<Boolean> add(@RequestBody Warehouse warehouse) {
        return CommonResult.success(warehouseService.save(warehouse));
    }
    
    @PutMapping
    public CommonResult<Boolean> update(@RequestBody Warehouse warehouse) {
        return CommonResult.success(warehouseService.updateById(warehouse));
    }
    
    @DeleteMapping("/{id}")
    public CommonResult<Boolean> delete(@PathVariable Long id) {
        return CommonResult.success(warehouseService.removeById(id));
    }
    
    @PutMapping("/{id}/status")
    public CommonResult<Boolean> updateStatus(@PathVariable Long id, @RequestParam String status) {
        return CommonResult.success(warehouseService.updateStatus(id, status));
    }
    
    @GetMapping("/code/generate")
    public CommonResult<String> generateCode() {
        return CommonResult.success(warehouseService.generateCode());
    }
}

backend\src\main\java\com\example\sales\entity\Customer.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Data
@TableName("cust_info")
public class Customer {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long categoryId;
    private String customerName;
    private String contactPerson;
    private String contactPhone;
    private String email;
    private String address;
    private String industry;
    private String companySize;
    private BigDecimal annualRevenue;
    private String source;
    private String status;
    private String creditRating;
    private String remarks;
    private Long createBy;
    
    @TableField(exist = false)
    private String categoryName;
    
    @TableField(exist = false)
    private List<CustomerTag> tags;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\example\sales\entity\CustomerCategory.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("cust_category")
public class CustomerCategory {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String categoryName;
    private String description;
    private Integer sort;
    private Boolean enabled;
}

backend\src\main\java\com\example\sales\entity\CustomerConversion.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("customer_conversion")
public class CustomerConversion {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Integer year;
    private Integer month;
    private Integer totalLeads;
    private Integer convertedLeads;
    private BigDecimal conversionRate;
    private Integer avgConversionTime;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\CustomerFollowRecord.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("cust_follow_record")
public class CustomerFollowRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long customerId;
    private String content;
    private String contactPerson;
    private String contactMethod;
    private LocalDateTime followTime;
    private LocalDateTime nextFollowTime;
    private String followResult;
    private Long createdBy;
    private LocalDateTime createdTime;
}

backend\src\main\java\com\example\sales\entity\CustomerTag.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("cust_tag")
public class CustomerTag {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String tagName;
    private String color;
    private Integer sort;
    private Boolean enabled;
}

backend\src\main\java\com\example\sales\entity\InventoryTransaction.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("inventory_transaction")
public class InventoryTransaction {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long productId;
    
    private String warehouseCode;
    
    private String transactionType;
    
    private Integer quantity;
    
    private String referenceType;
    
    private Long referenceId;
    
    private String remark;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    @TableField(exist = false)
    private String productName;
    
    @TableField(exist = false)
    private String warehouseName;
}

backend\src\main\java\com\example\sales\entity\Invoice.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Data
@TableName("invoice")
public class Invoice {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String invoiceNo;
    private Long customerId;
    private Long orderId;
    private String invoiceType;
    private BigDecimal invoiceAmount;
    private BigDecimal taxRate;
    private BigDecimal taxAmount;
    private String status;
    private String invoiceTitle;
    private String taxNo;
    private String bankName;
    private String bankAccount;
    private String contactPhone;
    private String mailingAddress;
    private String remark;
    private Long createdBy;
    private LocalDateTime createdTime;
    private Long updatedBy;
    private LocalDateTime updatedTime;

    @TableField(exist = false)
    private List<InvoiceItem> items;
    @TableField(exist = false)
    private String customerName;
    @TableField(exist = false)
    private String orderNo;
}

backend\src\main\java\com\example\sales\entity\InvoiceItem.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@Data
@TableName("invoice_item")
public class InvoiceItem {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long invoiceId;
    private Long productId;
    private String productName;
    private String specification;
    private String unit;
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal totalPrice;
    private BigDecimal taxRate;
    private BigDecimal taxAmount;
}

backend\src\main\java\com\example\sales\entity\Product.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String productCode;
    
    private String productName;
    
    private Long categoryId;
    
    private String brand;
    
    private String model;
    
    private String specification;
    
    private String unit;
    
    private String description;
    
    private String status;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    private String updatedBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
    
    @TableField(exist = false)
    private String categoryName;
}

backend\src\main\java\com\example\sales\entity\ProductCategory.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("product_category")
public class ProductCategory {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String categoryName;
    
    private Long parentId;
    
    private Integer level;
    
    private Integer sortOrder;
    
    private String status;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    private String updatedBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\ProductInventory.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("product_inventory")
public class ProductInventory {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long productId;
    
    private String warehouseCode;
    
    private Integer quantity;
    
    private Integer lockedQuantity;
    
    private Integer alertThreshold;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    private String updatedBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
    
    @TableField(exist = false)
    private String productName;
    
    @TableField(exist = false)
    private String warehouseName;
    
    @TableField(exist = false)
    private Integer availableQuantity;
    
    public Integer getAvailableQuantity() {
        return quantity - lockedQuantity;
    }
}

backend\src\main\java\com\example\sales\entity\ProductPriceStrategy.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("product_price_strategy")
public class ProductPriceStrategy {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long productId;
    
    private String strategyName;
    
    private Long customerCategoryId;
    
    private Integer minQuantity;
    
    private BigDecimal basePrice;
    
    private BigDecimal discountRate;
    
    private LocalDate startDate;
    
    private LocalDate endDate;
    
    private String status;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    private String updatedBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
    
    @TableField(exist = false)
    private String productName;
    
    @TableField(exist = false)
    private String customerCategoryName;
}

backend\src\main\java\com\example\sales\entity\ProductSalesTrend.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("product_sales_trend")
public class ProductSalesTrend {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long productId;
    private Long categoryId;
    private Integer year;
    private Integer month;
    private Integer salesQuantity;
    private BigDecimal salesAmount;
    private BigDecimal profitAmount;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\Quotation.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Data
@TableName("quotation")
public class Quotation {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String quotationNo;
    private Long customerId;
    private BigDecimal totalAmount;
    private Integer validDays;
    private String status;
    private String remark;
    private Long createdBy;
    private LocalDateTime createdTime;
    private Long updatedBy;
    private LocalDateTime updatedTime;

    @TableField(exist = false)
    private List<QuotationItem> items;
    @TableField(exist = false)
    private String customerName;
}

backend\src\main\java\com\example\sales\entity\QuotationItem.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@Data
@TableName("quotation_item")
public class QuotationItem {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long quotationId;
    private Long productId;
    private String productName;
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal totalPrice;
    private String remark;
}

backend\src\main\java\com\example\sales\entity\RealtimeMetrics.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("realtime_metrics")
public class RealtimeMetrics {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String metricCode;
    private String metricName;
    private BigDecimal metricValue;
    private String metricUnit;
    private BigDecimal compareValue;
    private BigDecimal compareRate;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\RegionalSales.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("regional_sales")
public class RegionalSales {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String province;
    private String city;
    private Integer year;
    private Integer month;
    private Integer customerCount;
    private BigDecimal contractAmount;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\SalesContract.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("sales_contract")
public class SalesContract {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String contractNo;
    private Long customerId;
    private Long orderId;
    private BigDecimal contractAmount;
    private LocalDate signDate;
    private LocalDate startDate;
    private LocalDate endDate;
    private String status;
    private String content;
    private String attachmentUrl;
    private String remark;
    private Long createdBy;
    private LocalDateTime createdTime;
    private Long updatedBy;
    private LocalDateTime updatedTime;

    @TableField(exist = false)
    private String customerName;
    @TableField(exist = false)
    private String orderNo;
}

backend\src\main\java\com\example\sales\entity\SalesFunnel.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("sales_funnel")
public class SalesFunnel {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String stage;
    private Integer year;
    private Integer month;
    private Integer customerCount;
    private BigDecimal opportunityAmount;
    private BigDecimal conversionRate;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\SalesOrder.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Data
@TableName("sales_order")
public class SalesOrder {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String orderNo;
    private Long customerId;
    private BigDecimal totalAmount;
    private BigDecimal discountAmount;
    private BigDecimal actualAmount;
    private String status;
    private String paymentStatus;
    private String deliveryStatus;
    private String remark;
    private Long createdBy;
    private LocalDateTime createdTime;
    private Long updatedBy;
    private LocalDateTime updatedTime;

    @TableField(exist = false)
    private List<SalesOrderItem> items;
    @TableField(exist = false)
    private String customerName;
}

backend\src\main\java\com\example\sales\entity\SalesOrderItem.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@Data
@TableName("sales_order_item")
public class SalesOrderItem {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long orderId;
    private Long productId;
    private String productName;
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal totalPrice;
    private String remark;
}

backend\src\main\java\com\example\sales\entity\SalesPerformance.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("sales_performance")
public class SalesPerformance {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long salesUserId;
    private Integer year;
    private Integer month;
    private Integer contractCount;
    private BigDecimal contractAmount;
    private BigDecimal receivedAmount;
    private BigDecimal commission;
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\entity\SysPermission.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_permission")
public class SysPermission {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long parentId;
    private String name;
    private String permissionCode;
    private String path;
    private String component;
    private Integer type;
    private String icon;
    private Integer sortOrder;
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\example\sales\entity\SysRole.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_role")
public class SysRole {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String roleName;
    private String roleCode;
    private String description;
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\example\sales\entity\SysUser.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_user")
public class SysUser {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String realName;
    private String email;
    private String phone;
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\example\sales\entity\Warehouse.java

package com.example.sales.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("warehouse")
public class Warehouse {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String warehouseCode;
    
    private String warehouseName;
    
    private String contactPerson;
    
    private String contactPhone;
    
    private String province;
    
    private String city;
    
    private String district;
    
    private String address;
    
    private String status;
    
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdTime;
    
    private String updatedBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedTime;
}

backend\src\main\java\com\example\sales\security\JwtAuthenticationFilter.java

package com.example.sales.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String username = tokenProvider.getUsernameFromToken(jwt);
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            log.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

backend\src\main\java\com\example\sales\security\JwtTokenProvider.java

package com.example.sales.security;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expiration}")
    private long jwtExpirationInMs;

    private SecretKey key;

    public String generateToken(Authentication authentication) {
        UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

        return Jwts.builder()
                .setSubject(userPrincipal.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(getKey())
                .compact();
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getKey())
                .build()
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(getKey())
                .build()
                .parseClaimsJws(authToken);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }

    private SecretKey getKey() {
        if (key == null) {
            key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
        }
        return key;
    }
}

backend\src\main\java\com\example\sales\service\CustomerCategoryService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.CustomerCategory;

public interface CustomerCategoryService extends IService<CustomerCategory> {
    IPage<CustomerCategory> getPage(Integer pageNum, Integer pageSize, String keyword);
}

backend\src\main\java\com\example\sales\service\CustomerTagService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.CustomerTag;

public interface CustomerTagService extends IService<CustomerTag> {
    IPage<CustomerTag> getPage(Integer pageNum, Integer pageSize, String keyword);
}

backend\src\main\java\com\example\sales\service\DataAnalysisService.java

package com.example.sales.service;

import com.example.sales.vo.analysis.*;
import java.util.List;
import java.util.Map;

public interface DataAnalysisService {
    
    /**
     * 获取销售业绩数据
     */
    List<SalesPerformanceVO> getSalesPerformance(Integer year, Integer month, Long salesUserId);
    
    /**
     * 获取客户转化率数据
     */
    List<CustomerConversionVO> getCustomerConversion(Integer year, Integer month);
    
    /**
     * 获取产品销售趋势数据
     */
    List<ProductSalesTrendVO> getProductSalesTrend(Integer year, Integer month, Long productId, Long categoryId);
    
    /**
     * 获取实时概览数据
     */
    Map<String, Object> getRealtimeOverview();
    
    /**
     * 获取实时趋势数据
     */
    Map<String, Object> getRealtimeTrend(String metricCode, Integer hours);
    
    /**
     * 获取销售漏斗数据
     */
    List<SalesFunnelVO> getSalesFunnel(Integer year, Integer month);
    
    /**
     * 获取地区销售数据
     */
    List<RegionalSalesVO> getRegionalSales(Integer year, Integer month);
    
    /**
     * 获取仪表盘数据
     */
    Map<String, Object> getDashboardData();
}

backend\src\main\java\com\example\sales\service\InvoiceService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.Invoice;
import com.example.sales.vo.InvoiceQueryVO;

public interface InvoiceService extends IService<Invoice> {
    IPage<Invoice> getPage(InvoiceQueryVO queryVO);
    
    Invoice getInvoiceById(Long id);
    
    boolean saveInvoice(Invoice invoice);
    
    boolean updateInvoice(Invoice invoice);
    
    boolean submitInvoice(Long id);
    
    boolean issueInvoice(Long id);
    
    boolean mailInvoice(Long id);
    
    boolean voidInvoice(Long id, String reason);
}

backend\src\main\java\com\example\sales\service\QuotationService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.Quotation;
import com.example.sales.vo.QuotationQueryVO;

public interface QuotationService extends IService<Quotation> {
    IPage<Quotation> getPage(QuotationQueryVO queryVO);
    
    Quotation getQuotationById(Long id);
    
    boolean saveQuotation(Quotation quotation);
    
    boolean updateQuotation(Quotation quotation);
    
    boolean sendQuotation(Long id);
    
    boolean acceptQuotation(Long id);
    
    boolean rejectQuotation(Long id, String reason);
}

backend\src\main\java\com\example\sales\service\SalesContractService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.SalesContract;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.web.multipart.MultipartFile;

public interface SalesContractService extends IService<SalesContract> {
    IPage<SalesContract> getPage(ContractQueryVO queryVO);
    
    SalesContract getContractById(Long id);
    
    boolean saveContract(SalesContract contract);
    
    boolean updateContract(SalesContract contract);
    
    boolean signContract(Long id);
    
    boolean terminateContract(Long id, String reason);
    
    String uploadAttachment(MultipartFile file);
}

backend\src\main\java\com\example\sales\service\SalesOrderService.java

package com.example.sales.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.sales.entity.SalesOrder;
import com.example.sales.vo.SalesOrderQueryVO;

public interface SalesOrderService extends IService<SalesOrder> {
    IPage<SalesOrder> getPage(SalesOrderQueryVO queryVO);
    
    SalesOrder getOrderById(Long id);
    
    boolean saveOrder(SalesOrder order);
    
    boolean updateOrder(SalesOrder order);
    
    boolean submitOrder(Long id);
    
    boolean approveOrder(Long id);
    
    boolean rejectOrder(Long id, String reason);
}

backend\src\main\java\com\example\sales\service\impl\CustomerCategoryServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.CustomerCategory;
import com.example.sales.mapper.CustomerCategoryMapper;
import com.example.sales.service.CustomerCategoryService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
public class CustomerCategoryServiceImpl extends ServiceImpl<CustomerCategoryMapper, CustomerCategory> implements CustomerCategoryService {
    
    @Override
    public IPage<CustomerCategory> getPage(Integer pageNum, Integer pageSize, String keyword) {
        LambdaQueryWrapper<CustomerCategory> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.hasText(keyword)) {
            wrapper.like(CustomerCategory::getCategoryName, keyword);
        }
        wrapper.orderByAsc(CustomerCategory::getSort);
        return this.page(new Page<>(pageNum, pageSize), wrapper);
    }
}

backend\src\main\java\com\example\sales\service\impl\CustomerServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Customer;
import com.example.sales.entity.CustomerFollowRecord;
import com.example.sales.entity.CustomerTag;
import com.example.sales.mapper.CustomerMapper;
import com.example.sales.service.CustomerService;
import com.example.sales.vo.CustomerAnalysisVO;
import com.example.sales.vo.CustomerQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService {

    @Autowired
    private CustomerTagService customerTagService;

    @Autowired
    private CustomerFollowRecordService followRecordService;

    @Override
    public IPage<Customer> getCustomerPage(CustomerQueryVO queryVO) {
        LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
        
        // 构建查询条件
        if (StringUtils.hasText(queryVO.getKeyword())) {
            wrapper.like(Customer::getCustomerName, queryVO.getKeyword())
                   .or()
                   .like(Customer::getContactPerson, queryVO.getKeyword());
        }
        if (queryVO.getCategoryId() != null) {
            wrapper.eq(Customer::getCategoryId, queryVO.getCategoryId());
        }
        if (StringUtils.hasText(queryVO.getStatus())) {
            wrapper.eq(Customer::getStatus, queryVO.getStatus());
        }
        
        wrapper.orderByDesc(Customer::getCreateTime);

        // 分页查询
        Page<Customer> page = new Page<>(queryVO.getPageNum(), queryVO.getPageSize());
        IPage<Customer> customerPage = this.page(page, wrapper);

        // 填充额外信息
        customerPage.getRecords().forEach(this::fillCustomerInfo);

        return customerPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveCustomer(Customer customer) {
        // 保存客户基本信息
        boolean success = this.save(customer);
        
        // 保存标签关联
        if (success && customer.getTags() != null) {
            customerTagService.saveBatch(customer.getTags().stream()
                .map(tag -> {
                    tag.setCustomerId(customer.getId());
                    return tag;
                })
                .collect(Collectors.toList()));
        }
        
        return success;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateCustomer(Customer customer) {
        // 更新客户基本信息
        boolean success = this.updateById(customer);
        
        if (success && customer.getTags() != null) {
            // 删除原有标签关联
            customerTagService.remove(new LambdaQueryWrapper<CustomerTag>()
                .eq(CustomerTag::getCustomerId, customer.getId()));
            
            // 保存新的标签关联
            customerTagService.saveBatch(customer.getTags().stream()
                .map(tag -> {
                    tag.setCustomerId(customer.getId());
                    return tag;
                })
                .collect(Collectors.toList()));
        }
        
        return success;
    }

    @Override
    public boolean addFollowRecord(CustomerFollowRecord record) {
        return followRecordService.save(record);
    }

    @Override
    public List<CustomerFollowRecord> getFollowRecords(Long customerId) {
        return followRecordService.list(new LambdaQueryWrapper<CustomerFollowRecord>()
            .eq(CustomerFollowRecord::getCustomerId, customerId)
            .orderByDesc(CustomerFollowRecord::getCreateTime));
    }

    @Override
    public CustomerAnalysisVO getCustomerAnalysis() {
        CustomerAnalysisVO analysisVO = new CustomerAnalysisVO();
        
        // 统计客户总数
        analysisVO.setTotalCustomers(this.count());
        
        // 统计各状态客户数量
        Map<String, Long> statusCount = this.list().stream()
            .collect(Collectors.groupingBy(Customer::getStatus, Collectors.counting()));
        analysisVO.setStatusDistribution(statusCount);
        
        // 统计本月新增客户数
        analysisVO.setNewCustomersThisMonth(this.count(new LambdaQueryWrapper<Customer>()
            .apply("DATE_FORMAT(create_time,'%Y%m') = DATE_FORMAT(NOW(),'%Y%m')")));
        
        return analysisVO;
    }

    @Override
    public Map<String, Object> getCustomerDistribution() {
        Map<String, Object> distribution = new HashMap<>();
        
        // 行业分布
        distribution.put("industryDistribution", this.list().stream()
            .collect(Collectors.groupingBy(Customer::getIndustry, Collectors.counting())));
        
        // 规模分布
        distribution.put("sizeDistribution", this.list().stream()
            .collect(Collectors.groupingBy(Customer::getCompanySize, Collectors.counting())));
        
        // 来源分布
        distribution.put("sourceDistribution", this.list().stream()
            .collect(Collectors.groupingBy(Customer::getSource, Collectors.counting())));
        
        return distribution;
    }

    private void fillCustomerInfo(Customer customer) {
        // 填充标签信息
        List<CustomerTag> tags = customerTagService.list(new LambdaQueryWrapper<CustomerTag>()
            .eq(CustomerTag::getCustomerId, customer.getId()));
        customer.setTags(tags);
        
        // 可以填充其他需要的信息
    }
}

backend\src\main\java\com\example\sales\service\impl\CustomerTagServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.CustomerTag;
import com.example.sales.mapper.CustomerTagMapper;
import com.example.sales.service.CustomerTagService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
public class CustomerTagServiceImpl extends ServiceImpl<CustomerTagMapper, CustomerTag> implements CustomerTagService {
    
    @Override
    public IPage<CustomerTag> getPage(Integer pageNum, Integer pageSize, String keyword) {
        LambdaQueryWrapper<CustomerTag> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.hasText(keyword)) {
            wrapper.like(CustomerTag::getTagName, keyword);
        }
        wrapper.orderByAsc(CustomerTag::getSort);
        return this.page(new Page<>(pageNum, pageSize), wrapper);
    }
}

backend\src\main\java\com\example\sales\service\impl\DataAnalysisServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.sales.entity.*;
import com.example.sales.mapper.*;
import com.example.sales.service.DataAnalysisService;
import com.example.sales.vo.analysis.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class DataAnalysisServiceImpl implements DataAnalysisService {

    @Autowired
    private SalesPerformanceMapper salesPerformanceMapper;
    
    @Autowired
    private CustomerConversionMapper customerConversionMapper;
    
    @Autowired
    private ProductSalesTrendMapper productSalesTrendMapper;
    
    @Autowired
    private RealtimeMetricsMapper realtimeMetricsMapper;
    
    @Autowired
    private SalesFunnelMapper salesFunnelMapper;
    
    @Autowired
    private RegionalSalesMapper regionalSalesMapper;

    @Override
    public List<SalesPerformanceVO> getSalesPerformance(Integer year, Integer month, Long salesUserId) {
        LambdaQueryWrapper<SalesPerformance> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SalesPerformance::getYear, year);
        if (month != null) {
            wrapper.eq(SalesPerformance::getMonth, month);
        }
        if (salesUserId != null) {
            wrapper.eq(SalesPerformance::getSalesUserId, salesUserId);
        }
        wrapper.orderByAsc(SalesPerformance::getMonth);
        
        List<SalesPerformance> list = salesPerformanceMapper.selectList(wrapper);
        return list.stream().map(item -> {
            SalesPerformanceVO vo = new SalesPerformanceVO();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<CustomerConversionVO> getCustomerConversion(Integer year, Integer month) {
        LambdaQueryWrapper<CustomerConversion> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CustomerConversion::getYear, year);
        if (month != null) {
            wrapper.eq(CustomerConversion::getMonth, month);
        }
        wrapper.orderByAsc(CustomerConversion::getMonth);
        
        List<CustomerConversion> list = customerConversionMapper.selectList(wrapper);
        return list.stream().map(item -> {
            CustomerConversionVO vo = new CustomerConversionVO();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<ProductSalesTrendVO> getProductSalesTrend(Integer year, Integer month, Long productId, Long categoryId) {
        LambdaQueryWrapper<ProductSalesTrend> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductSalesTrend::getYear, year);
        if (month != null) {
            wrapper.eq(ProductSalesTrend::getMonth, month);
        }
        if (productId != null) {
            wrapper.eq(ProductSalesTrend::getProductId, productId);
        }
        if (categoryId != null) {
            wrapper.eq(ProductSalesTrend::getCategoryId, categoryId);
        }
        wrapper.orderByAsc(ProductSalesTrend::getMonth);
        
        List<ProductSalesTrend> list = productSalesTrendMapper.selectList(wrapper);
        return list.stream().map(item -> {
            ProductSalesTrendVO vo = new ProductSalesTrendVO();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getRealtimeOverview() {
        List<RealtimeMetrics> metrics = realtimeMetricsMapper.selectList(null);
        Map<String, Object> result = new HashMap<>();
        metrics.forEach(metric -> {
            Map<String, Object> metricData = new HashMap<>();
            metricData.put("value", metric.getMetricValue());
            metricData.put("unit", metric.getMetricUnit());
            metricData.put("compareValue", metric.getCompareValue());
            metricData.put("compareRate", metric.getCompareRate());
            result.put(metric.getMetricCode(), metricData);
        });
        return result;
    }

    @Override
    public Map<String, Object> getRealtimeTrend(String metricCode, Integer hours) {
        // 实现实时趋势数据查询,这里需要结合具体的时序数据库实现
        // 可以使用 InfluxDB, Prometheus 等时序数据库
        return new HashMap<>();
    }

    @Override
    public List<SalesFunnelVO> getSalesFunnel(Integer year, Integer month) {
        LambdaQueryWrapper<SalesFunnel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SalesFunnel::getYear, year);
        if (month != null) {
            wrapper.eq(SalesFunnel::getMonth, month);
        }
        wrapper.orderByAsc(SalesFunnel::getStage);
        
        List<SalesFunnel> list = salesFunnelMapper.selectList(wrapper);
        return list.stream().map(item -> {
            SalesFunnelVO vo = new SalesFunnelVO();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public List<RegionalSalesVO> getRegionalSales(Integer year, Integer month) {
        LambdaQueryWrapper<RegionalSales> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RegionalSales::getYear, year);
        if (month != null) {
            wrapper.eq(RegionalSales::getMonth, month);
        }
        wrapper.orderByDesc(RegionalSales::getContractAmount);
        
        List<RegionalSales> list = regionalSalesMapper.selectList(wrapper);
        return list.stream().map(item -> {
            RegionalSalesVO vo = new RegionalSalesVO();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getDashboardData() {
        Map<String, Object> result = new HashMap<>();
        
        // 1. 获取实时概览数据
        result.put("overview", getRealtimeOverview());
        
        // 2. 获取今年的销售业绩
        int currentYear = LocalDateTime.now().getYear();
        result.put("performance", getSalesPerformance(currentYear, null, null));
        
        // 3. 获取今年的客户转化率
        result.put("conversion", getCustomerConversion(currentYear, null));
        
        // 4. 获取销售漏斗数据
        result.put("funnel", getSalesFunnel(currentYear, null));
        
        // 5. 获取地区销售数据
        result.put("regional", getRegionalSales(currentYear, null));
        
        return result;
    }
}

backend\src\main\java\com\example\sales\service\impl\InvoiceServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Invoice;
import com.example.sales.entity.InvoiceItem;
import com.example.sales.mapper.InvoiceMapper;
import com.example.sales.service.InvoiceItemService;
import com.example.sales.service.InvoiceService;
import com.example.sales.vo.InvoiceQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

@Service
public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> implements InvoiceService {

    @Autowired
    private InvoiceItemService invoiceItemService;

    @Override
    public IPage<Invoice> getPage(InvoiceQueryVO queryVO) {
        LambdaQueryWrapper<Invoice> wrapper = new LambdaQueryWrapper<>();
        // 根据发票号查询
        if (StringUtils.hasText(queryVO.getInvoiceNo())) {
            wrapper.eq(Invoice::getInvoiceNo, queryVO.getInvoiceNo());
        }
        // 根据客户ID查询
        if (queryVO.getCustomerId() != null) {
            wrapper.eq(Invoice::getCustomerId, queryVO.getCustomerId());
        }
        // 根据状态查询
        if (StringUtils.hasText(queryVO.getStatus())) {
            wrapper.eq(Invoice::getStatus, queryVO.getStatus());
        }
        // 根据发票类型查询
        if (StringUtils.hasText(queryVO.getInvoiceType())) {
            wrapper.eq(Invoice::getInvoiceType, queryVO.getInvoiceType());
        }
        // 根据创建时间范围查询
        if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
            wrapper.between(Invoice::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
        }
        // 按创建时间倒序排序
        wrapper.orderByDesc(Invoice::getCreatedTime);
        
        return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
    }

    @Override
    public Invoice getInvoiceById(Long id) {
        Invoice invoice = this.getById(id);
        if (invoice != null) {
            // 查询发票明细
            LambdaQueryWrapper<InvoiceItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(InvoiceItem::getInvoiceId, id);
            invoice.setItems(invoiceItemService.list(wrapper));
        }
        return invoice;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveInvoice(Invoice invoice) {
        // 设置初始状态
        invoice.setStatus("DRAFT");
        invoice.setCreatedTime(LocalDateTime.now());
        // 生成发票编号
        invoice.setInvoiceNo("INV" + System.currentTimeMillis());
        
        // 保存发票
        boolean success = this.save(invoice);
        if (success && invoice.getItems() != null && !invoice.getItems().isEmpty()) {
            // 保存发票明细
            invoice.getItems().forEach(item -> item.setInvoiceId(invoice.getId()));
            invoiceItemService.saveBatch(invoice.getItems());
        }
        return success;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateInvoice(Invoice invoice) {
        invoice.setUpdatedTime(LocalDateTime.now());
        
        // 更新发票
        boolean success = this.updateById(invoice);
        if (success && invoice.getItems() != null && !invoice.getItems().isEmpty()) {
            // 删除原有明细
            LambdaQueryWrapper<InvoiceItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(InvoiceItem::getInvoiceId, invoice.getId());
            invoiceItemService.remove(wrapper);
            
            // 保存新的明细
            invoice.getItems().forEach(item -> item.setInvoiceId(invoice.getId()));
            invoiceItemService.saveBatch(invoice.getItems());
        }
        return success;
    }

    @Override
    public boolean submitInvoice(Long id) {
        Invoice invoice = this.getById(id);
        if (invoice != null && "DRAFT".equals(invoice.getStatus())) {
            invoice.setStatus("SUBMITTED");
            invoice.setUpdatedTime(LocalDateTime.now());
            return this.updateById(invoice);
        }
        return false;
    }

    @Override
    public boolean issueInvoice(Long id) {
        Invoice invoice = this.getById(id);
        if (invoice != null && "SUBMITTED".equals(invoice.getStatus())) {
            invoice.setStatus("ISSUED");
            invoice.setUpdatedTime(LocalDateTime.now());
            return this.updateById(invoice);
        }
        return false;
    }

    @Override
    public boolean mailInvoice(Long id) {
        Invoice invoice = this.getById(id);
        if (invoice != null && "ISSUED".equals(invoice.getStatus())) {
            invoice.setStatus("MAILED");
            invoice.setUpdatedTime(LocalDateTime.now());
            return this.updateById(invoice);
        }
        return false;
    }

    @Override
    public boolean voidInvoice(Long id, String reason) {
        Invoice invoice = this.getById(id);
        if (invoice != null && !"VOID".equals(invoice.getStatus())) {
            invoice.setStatus("VOID");
            invoice.setRemark(reason);
            invoice.setUpdatedTime(LocalDateTime.now());
            return this.updateById(invoice);
        }
        return false;
    }
}

backend\src\main\java\com\example\sales\service\impl\QuotationServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.Quotation;
import com.example.sales.entity.QuotationItem;
import com.example.sales.mapper.QuotationMapper;
import com.example.sales.service.QuotationItemService;
import com.example.sales.service.QuotationService;
import com.example.sales.vo.QuotationQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

@Service
public class QuotationServiceImpl extends ServiceImpl<QuotationMapper, Quotation> implements QuotationService {

    @Autowired
    private QuotationItemService quotationItemService;

    @Override
    public IPage<Quotation> getPage(QuotationQueryVO queryVO) {
        LambdaQueryWrapper<Quotation> wrapper = new LambdaQueryWrapper<>();
        // 根据报价单号查询
        if (StringUtils.hasText(queryVO.getQuotationNo())) {
            wrapper.eq(Quotation::getQuotationNo, queryVO.getQuotationNo());
        }
        // 根据客户ID查询
        if (queryVO.getCustomerId() != null) {
            wrapper.eq(Quotation::getCustomerId, queryVO.getCustomerId());
        }
        // 根据状态查询
        if (StringUtils.hasText(queryVO.getStatus())) {
            wrapper.eq(Quotation::getStatus, queryVO.getStatus());
        }
        // 根据创建时间范围查询
        if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
            wrapper.between(Quotation::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
        }
        // 按创建时间倒序排序
        wrapper.orderByDesc(Quotation::getCreatedTime);
        
        return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
    }

    @Override
    public Quotation getQuotationById(Long id) {
        Quotation quotation = this.getById(id);
        if (quotation != null) {
            // 查询报价单明细
            LambdaQueryWrapper<QuotationItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(QuotationItem::getQuotationId, id);
            quotation.setItems(quotationItemService.list(wrapper));
        }
        return quotation;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveQuotation(Quotation quotation) {
        // 设置初始状态
        quotation.setStatus("DRAFT");
        quotation.setCreatedTime(LocalDateTime.now());
        
        // 保存报价单
        boolean success = this.save(quotation);
        if (success && quotation.getItems() != null && !quotation.getItems().isEmpty()) {
            // 保存报价单明细
            quotation.getItems().forEach(item -> item.setQuotationId(quotation.getId()));
            quotationItemService.saveBatch(quotation.getItems());
        }
        return success;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateQuotation(Quotation quotation) {
        quotation.setUpdatedTime(LocalDateTime.now());
        
        // 更新报价单
        boolean success = this.updateById(quotation);
        if (success && quotation.getItems() != null && !quotation.getItems().isEmpty()) {
            // 删除原有明细
            LambdaQueryWrapper<QuotationItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(QuotationItem::getQuotationId, quotation.getId());
            quotationItemService.remove(wrapper);
            
            // 保存新的明细
            quotation.getItems().forEach(item -> item.setQuotationId(quotation.getId()));
            quotationItemService.saveBatch(quotation.getItems());
        }
        return success;
    }

    @Override
    public boolean sendQuotation(Long id) {
        Quotation quotation = this.getById(id);
        if (quotation != null && "DRAFT".equals(quotation.getStatus())) {
            quotation.setStatus("SENT");
            quotation.setUpdatedTime(LocalDateTime.now());
            return this.updateById(quotation);
        }
        return false;
    }

    @Override
    public boolean acceptQuotation(Long id) {
        Quotation quotation = this.getById(id);
        if (quotation != null && "SENT".equals(quotation.getStatus())) {
            quotation.setStatus("ACCEPTED");
            quotation.setUpdatedTime(LocalDateTime.now());
            return this.updateById(quotation);
        }
        return false;
    }

    @Override
    public boolean rejectQuotation(Long id, String reason) {
        Quotation quotation = this.getById(id);
        if (quotation != null && "SENT".equals(quotation.getStatus())) {
            quotation.setStatus("REJECTED");
            quotation.setRemark(reason);
            quotation.setUpdatedTime(LocalDateTime.now());
            return this.updateById(quotation);
        }
        return false;
    }
}

backend\src\main\java\com\example\sales\service\impl\SalesContractServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.SalesContract;
import com.example.sales.mapper.SalesContractMapper;
import com.example.sales.service.SalesContractService;
import com.example.sales.vo.ContractQueryVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.UUID;

@Service
public class SalesContractServiceImpl extends ServiceImpl<SalesContractMapper, SalesContract> implements SalesContractService {

    @Override
    public IPage<SalesContract> getPage(ContractQueryVO queryVO) {
        LambdaQueryWrapper<SalesContract> wrapper = new LambdaQueryWrapper<>();
        // 根据合同编号查询
        if (StringUtils.hasText(queryVO.getContractNo())) {
            wrapper.eq(SalesContract::getContractNo, queryVO.getContractNo());
        }
        // 根据客户ID查询
        if (queryVO.getCustomerId() != null) {
            wrapper.eq(SalesContract::getCustomerId, queryVO.getCustomerId());
        }
        // 根据状态查询
        if (StringUtils.hasText(queryVO.getStatus())) {
            wrapper.eq(SalesContract::getStatus, queryVO.getStatus());
        }
        // 根据签订时间范围查询
        if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
            wrapper.between(SalesContract::getSignDate, queryVO.getStartDate(), queryVO.getEndDate());
        }
        // 按创建时间倒序排序
        wrapper.orderByDesc(SalesContract::getCreatedTime);
        
        return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
    }

    @Override
    public SalesContract getContractById(Long id) {
        return this.getById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveContract(SalesContract contract) {
        // 设置初始状态
        contract.setStatus("DRAFT");
        contract.setCreatedTime(LocalDateTime.now());
        // 生成合同编号
        contract.setContractNo("CT" + System.currentTimeMillis());
        
        return this.save(contract);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateContract(SalesContract contract) {
        contract.setUpdatedTime(LocalDateTime.now());
        return this.updateById(contract);
    }

    @Override
    public boolean signContract(Long id) {
        SalesContract contract = this.getById(id);
        if (contract != null && "DRAFT".equals(contract.getStatus())) {
            contract.setStatus("SIGNED");
            contract.setUpdatedTime(LocalDateTime.now());
            return this.updateById(contract);
        }
        return false;
    }

    @Override
    public boolean terminateContract(Long id, String reason) {
        SalesContract contract = this.getById(id);
        if (contract != null && "EXECUTING".equals(contract.getStatus())) {
            contract.setStatus("TERMINATED");
            contract.setRemark(reason);
            contract.setUpdatedTime(LocalDateTime.now());
            return this.updateById(contract);
        }
        return false;
    }

    @Override
    public String uploadAttachment(MultipartFile file) {
        // TODO: 实现文件上传逻辑
        String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
        // 这里需要实现具体的文件上传逻辑,可以是上传到本地文件系统或者云存储
        return "/uploads/" + fileName;
    }
}

backend\src\main\java\com\example\sales\service\impl\SalesOrderServiceImpl.java

package com.example.sales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.sales.entity.SalesOrder;
import com.example.sales.entity.SalesOrderItem;
import com.example.sales.mapper.SalesOrderMapper;
import com.example.sales.service.SalesOrderItemService;
import com.example.sales.service.SalesOrderService;
import com.example.sales.vo.SalesOrderQueryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

@Service
public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOrder> implements SalesOrderService {

    @Autowired
    private SalesOrderItemService orderItemService;

    @Override
    public IPage<SalesOrder> getPage(SalesOrderQueryVO queryVO) {
        LambdaQueryWrapper<SalesOrder> wrapper = new LambdaQueryWrapper<>();
        // 根据订单号查询
        if (StringUtils.hasText(queryVO.getOrderNo())) {
            wrapper.eq(SalesOrder::getOrderNo, queryVO.getOrderNo());
        }
        // 根据客户ID查询
        if (queryVO.getCustomerId() != null) {
            wrapper.eq(SalesOrder::getCustomerId, queryVO.getCustomerId());
        }
        // 根据状态查询
        if (StringUtils.hasText(queryVO.getStatus())) {
            wrapper.eq(SalesOrder::getStatus, queryVO.getStatus());
        }
        // 根据付款状态查询
        if (StringUtils.hasText(queryVO.getPaymentStatus())) {
            wrapper.eq(SalesOrder::getPaymentStatus, queryVO.getPaymentStatus());
        }
        // 根据发货状态查询
        if (StringUtils.hasText(queryVO.getDeliveryStatus())) {
            wrapper.eq(SalesOrder::getDeliveryStatus, queryVO.getDeliveryStatus());
        }
        // 根据创建时间范围查询
        if (StringUtils.hasText(queryVO.getStartDate()) && StringUtils.hasText(queryVO.getEndDate())) {
            wrapper.between(SalesOrder::getCreatedTime, queryVO.getStartDate(), queryVO.getEndDate());
        }
        // 按创建时间倒序排序
        wrapper.orderByDesc(SalesOrder::getCreatedTime);
        
        return this.page(new Page<>(queryVO.getPageNum(), queryVO.getPageSize()), wrapper);
    }

    @Override
    public SalesOrder getOrderById(Long id) {
        SalesOrder order = this.getById(id);
        if (order != null) {
            // 查询订单明细
            LambdaQueryWrapper<SalesOrderItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SalesOrderItem::getOrderId, id);
            order.setItems(orderItemService.list(wrapper));
        }
        return order;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrder(SalesOrder order) {
        // 设置初始状态
        order.setStatus("DRAFT");
        order.setPaymentStatus("UNPAID");
        order.setDeliveryStatus("UNDELIVERED");
        order.setCreatedTime(LocalDateTime.now());
        
        // 保存订单
        boolean success = this.save(order);
        if (success && order.getItems() != null && !order.getItems().isEmpty()) {
            // 保存订单明细
            order.getItems().forEach(item -> item.setOrderId(order.getId()));
            orderItemService.saveBatch(order.getItems());
        }
        return success;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateOrder(SalesOrder order) {
        order.setUpdatedTime(LocalDateTime.now());
        
        // 更新订单
        boolean success = this.updateById(order);
        if (success && order.getItems() != null && !order.getItems().isEmpty()) {
            // 删除原有明细
            LambdaQueryWrapper<SalesOrderItem> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SalesOrderItem::getOrderId, order.getId());
            orderItemService.remove(wrapper);
            
            // 保存新的明细
            order.getItems().forEach(item -> item.setOrderId(order.getId()));
            orderItemService.saveBatch(order.getItems());
        }
        return success;
    }

    @Override
    public boolean submitOrder(Long id) {
        SalesOrder order = this.getById(id);
        if (order != null && "DRAFT".equals(order.getStatus())) {
            order.setStatus("SUBMITTED");
            order.setUpdatedTime(LocalDateTime.now());
            return this.updateById(order);
        }
        return false;
    }

    @Override
    public boolean approveOrder(Long id) {
        SalesOrder order = this.getById(id);
        if (order != null && "SUBMITTED".equals(order.getStatus())) {
            order.setStatus("APPROVED");
            order.setUpdatedTime(LocalDateTime.now());
            return this.updateById(order);
        }
        return false;
    }

    @Override
    public boolean rejectOrder(Long id, String reason) {
        SalesOrder order = this.getById(id);
        if (order != null && "SUBMITTED".equals(order.getStatus())) {
            order.setStatus("REJECTED");
            order.setRemark(reason);
            order.setUpdatedTime(LocalDateTime.now());
            return this.updateById(order);
        }
        return false;
    }
}

backend\src\main\java\com\example\sales\vo\ContractQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class ContractQueryVO {
    private String keyword;
    private String contractNo;
    private Long customerId;
    private String status;
    private String startDate;
    private String endDate;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

backend\src\main\java\com\example\sales\vo\CustomerAnalysisVO.java

package com.example.sales.vo;

import lombok.Data;
import java.util.Map;

@Data
public class CustomerAnalysisVO {
    private Long totalCustomers;
    private Long newCustomersThisMonth;
    private Map<String, Long> statusDistribution;
    private Double conversionRate;
}

backend\src\main\java\com\example\sales\vo\CustomerQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class CustomerQueryVO {
    private String keyword;
    private Long categoryId;
    private String status;
    private Long tagId;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

backend\src\main\java\com\example\sales\vo\InvoiceQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class InvoiceQueryVO {
    private String keyword;
    private String invoiceNo;
    private Long customerId;
    private String status;
    private String invoiceType;
    private String startDate;
    private String endDate;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

backend\src\main\java\com\example\sales\vo\ProductCategoryQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class ProductCategoryQueryVO {
    private String categoryName;
    private Long parentId;
    private String status;
}

backend\src\main\java\com\example\sales\vo\ProductInventoryQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class ProductInventoryQueryVO {
    private Long productId;
    private String warehouseCode;
    private Boolean belowThreshold;
}

backend\src\main\java\com\example\sales\vo\ProductPriceStrategyQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class ProductPriceStrategyQueryVO {
    private Long productId;
    private String strategyName;
    private Long customerCategoryId;
    private String status;
}

backend\src\main\java\com\example\sales\vo\ProductQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class ProductQueryVO {
    private String productCode;
    private String productName;
    private Long categoryId;
    private String brand;
    private String status;
}

backend\src\main\java\com\example\sales\vo\QuotationQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class QuotationQueryVO {
    private String keyword;
    private String quotationNo;
    private Long customerId;
    private String status;
    private String startDate;
    private String endDate;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

backend\src\main\java\com\example\sales\vo\SalesOrderQueryVO.java

package com.example.sales.vo;

import lombok.Data;

@Data
public class SalesOrderQueryVO {
    private String keyword;
    private String orderNo;
    private Long customerId;
    private String status;
    private String paymentStatus;
    private String deliveryStatus;
    private String startDate;
    private String endDate;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

backend\src\main\resources\application.yml

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  application:
    name: enterprise-sales-system
  
  # 数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sales_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    druid:
      # 初始连接数
      initial-size: 5
      # 最小连接池数量
      min-idle: 10
      # 最大连接池数量
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      max-evictable-idle-time-millis: 900000
      # 配置检测连接是否有效
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
  
  # Redis配置
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0

# MyBatis Plus配置
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*Mapper.xml
  type-aliases-package: com.example.sales.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 主键类型
      id-type: auto
      # 逻辑删除字段
      logic-delete-field: deleted
      # 逻辑删除值
      logic-delete-value: 1
      # 逻辑未删除值
      logic-not-delete-value: 0

# JWT配置
jwt:
  # JWT加解密使用的密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # JWT的超期限时间(60*60*24)
  expiration: 86400
  # JWT负载中拿到开头
  tokenHead: Bearer

# 文件上传配置
file:
  # 文件上传路径
  upload-path: D:/upload/
  # 文件访问路径
  access-path: /upload/**
  # 文件访问URL前缀
  access-url: http://localhost:8080/api/upload/

# Knife4j配置
knife4j:
  enable: true
  setting:
    language: zh-CN
    enable-swagger-models: true
    enable-document-manage: true
    swagger-model-name: 实体类列表
  basic:
    enable: false

backend\target\classes\application.yml

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sales_management?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.sales.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

jwt:
  secret: your-secret-key
  expiration: 86400000 # 24小时

logging:
  level:
    com.example.sales: debug

frontend\package.json

{
  "name": "enterprise-sales-system",
  "version": "1.0.0",
  "description": "企业销售管理系统",
  "author": "example",
  "license": "MIT",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
  "dependencies": {
    "@element-plus/icons-vue": "^2.1.0",
    "axios": "^1.4.0",
    "echarts": "^5.4.3",
    "element-plus": "^2.3.9",
    "js-cookie": "^3.0.5",
    "nprogress": "^0.2.0",
    "pinia": "^2.1.6",
    "vue": "^3.3.4",
    "vue-router": "^4.2.4",
    "vue-echarts": "^6.6.1"
  },
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.3.2",
    "@vitejs/plugin-vue": "^4.2.3",
    "@vue/eslint-config-prettier": "^8.0.0",
    "eslint": "^8.46.0",
    "eslint-plugin-vue": "^9.16.1",
    "prettier": "^3.0.1",
    "sass": "^1.64.2",
    "unplugin-auto-import": "^0.16.6",
    "unplugin-vue-components": "^0.25.1",
    "vite": "^4.4.9"
  }
}

frontend\vite.config.js

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      }
    }
  }
})

frontend\src\api\analysis\index.js

import request from '@/utils/request'

export function getSalesPerformance(params) {
  return request({
    url: '/analysis/performance',
    method: 'get',
    params
  })
}

export function getCustomerConversion(params) {
  return request({
    url: '/analysis/conversion',
    method: 'get',
    params
  })
}

export function getProductSalesTrend(params) {
  return request({
    url: '/analysis/product/trend',
    method: 'get',
    params
  })
}

export function getRealtimeOverview() {
  return request({
    url: '/analysis/realtime/overview',
    method: 'get'
  })
}

export function getRealtimeTrend(params) {
  return request({
    url: '/analysis/realtime/trend',
    method: 'get',
    params
  })
}

export function getSalesFunnel(params) {
  return request({
    url: '/analysis/funnel',
    method: 'get',
    params
  })
}

export function getRegionalSales(params) {
  return request({
    url: '/analysis/regional',
    method: 'get',
    params
  })
}

export function getDashboardData() {
  return request({
    url: '/analysis/dashboard',
    method: 'get'
  })
}

frontend\src\api\customer\category.js

import request from '@/utils/request'

export function getCategoryList() {
  return request({
    url: '/customer/category/list',
    method: 'get'
  })
}

export function getCategoryPage(params) {
  return request({
    url: '/customer/category/page',
    method: 'get',
    params
  })
}

export function addCategory(data) {
  return request({
    url: '/customer/category',
    method: 'post',
    data
  })
}

export function updateCategory(data) {
  return request({
    url: '/customer/category',
    method: 'put',
    data
  })
}

export function deleteCategory(id) {
  return request({
    url: `/customer/category/${id}`,
    method: 'delete'
  })
}

frontend\src\api\customer\index.js

import request from '@/utils/request'

// 获取客户列表
export function getCustomerList(params) {
  return request({
    url: '/api/customer/list',
    method: 'get',
    params
  })
}

// 获取客户详情
export function getCustomerDetail(id) {
  return request({
    url: `/api/customer/${id}`,
    method: 'get'
  })
}

// 新增客户
export function addCustomer(data) {
  return request({
    url: '/api/customer',
    method: 'post',
    data
  })
}

// 更新客户
export function updateCustomer(data) {
  return request({
    url: '/api/customer',
    method: 'put',
    data
  })
}

// 删除客户
export function deleteCustomer(id) {
  return request({
    url: `/api/customer/${id}`,
    method: 'delete'
  })
}

// 添加跟进记录
export function addFollowRecord(data) {
  return request({
    url: '/api/customer/follow',
    method: 'post',
    data
  })
}

// 获取跟进记录列表
export function getFollowRecords(customerId) {
  return request({
    url: `/api/customer/follow/${customerId}`,
    method: 'get'
  })
}

// 获取客户分析概览
export function getAnalysisOverview() {
  return request({
    url: '/api/customer/analysis/overview',
    method: 'get'
  })
}

// 获取客户分布数据
export function getDistributionData() {
  return request({
    url: '/api/customer/analysis/distribution',
    method: 'get'
  })
}

// 获取客户趋势数据
export function getTrendData() {
  return request({
    url: '/api/customer/analysis/trend',
    method: 'get'
  })
}

frontend\src\api\customer\tag.js

import request from '@/utils/request'

export function getTagList() {
  return request({
    url: '/customer/tag/list',
    method: 'get'
  })
}

export function getTagPage(params) {
  return request({
    url: '/customer/tag/page',
    method: 'get',
    params
  })
}

export function addTag(data) {
  return request({
    url: '/customer/tag',
    method: 'post',
    data
  })
}

export function updateTag(data) {
  return request({
    url: '/customer/tag',
    method: 'put',
    data
  })
}

export function deleteTag(id) {
  return request({
    url: `/customer/tag/${id}`,
    method: 'delete'
  })
}

frontend\src\api\product\category.js

import request from '@/utils/request'

// 获取分类列表
export function listCategory(params) {
  return request({
    url: '/product/category/list',
    method: 'get',
    params
  })
}

// 获取分类树
export function listCategoryTree() {
  return request({
    url: '/product/category/tree',
    method: 'get'
  })
}

// 获取分类详情
export function getCategory(id) {
  return request({
    url: `/product/category/${id}`,
    method: 'get'
  })
}

// 新增分类
export function addCategory(data) {
  return request({
    url: '/product/category',
    method: 'post',
    data
  })
}

// 更新分类
export function updateCategory(data) {
  return request({
    url: '/product/category',
    method: 'put',
    data
  })
}

// 删除分类
export function deleteCategory(id) {
  return request({
    url: `/product/category/${id}`,
    method: 'delete'
  })
}

// 更新分类状态
export function updateCategoryStatus(id, status) {
  return request({
    url: `/product/category/${id}/status`,
    method: 'put',
    params: { status }
  })
}

frontend\src\api\product\index.js

import request from '@/utils/request'

// 获取产品分页列表
export function getProductPage(params) {
  return request({
    url: '/product/page',
    method: 'get',
    params
  })
}

// 获取产品详情
export function getProduct(id) {
  return request({
    url: `/product/${id}`,
    method: 'get'
  })
}

// 新增产品
export function addProduct(data) {
  return request({
    url: '/product',
    method: 'post',
    data
  })
}

// 更新产品
export function updateProduct(data) {
  return request({
    url: '/product',
    method: 'put',
    data
  })
}

// 删除产品
export function deleteProduct(id) {
  return request({
    url: `/product/${id}`,
    method: 'delete'
  })
}

// 更新产品状态
export function updateProductStatus(id, status) {
  return request({
    url: `/product/${id}/status`,
    method: 'put',
    params: { status }
  })
}

// 生成产品编码
export function generateProductCode() {
  return request({
    url: '/product/code/generate',
    method: 'get'
  })
}

// 导出产品
export function exportProduct(params) {
  return request({
    url: '/product/export',
    method: 'get',
    params,
    responseType: 'blob'
  })
}

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值