1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/products/API.md

10 KiB
Raw Blame History

Products API 文档

概述

Products API 提供轻量级的产品查询接口,专门为前端下拉框和自动完成功能设计。

特点:

  • 只返回 idname 字段,减少数据传输
  • 支持分页LimitOffset
  • 支持按名称模糊搜索
  • 需要 JWT 认证

基础路径: /api/v1/products/quick/


认证

所有接口都需要 JWT 认证。在请求头中添加:

Authorization: Bearer <access_token>

接口详情

获取产品列表

请求

GET /api/v1/products/quick/

查询参数

参数 类型 必填 说明
limit integer 返回结果数量,不提供则返回所有
offset integer 偏移量,默认 0
search string 按产品名称模糊搜索(不区分大小写)

请求示例

# 获取所有产品
curl -X GET "http://localhost:8000/api/v1/products/quick/" \
  -H "Authorization: Bearer YOUR_TOKEN"

# 分页获取前10条
curl -X GET "http://localhost:8000/api/v1/products/quick/?limit=10&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

# 按名称搜索
curl -X GET "http://localhost:8000/api/v1/products/quick/?search=布料" \
  -H "Authorization: Bearer YOUR_TOKEN"

# 搜索 + 分页
curl -X GET "http://localhost:8000/api/v1/products/quick/?search=布料&limit=5&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

成功响应 - 不分页 (200 OK)

{
  "count": 5,
  "results": [
    {
      "id": 1,
      "name": "纯棉布料"
    },
    {
      "id": 2,
      "name": "涤纶布料"
    },
    {
      "id": 3,
      "name": "丝绸布料"
    },
    {
      "id": 4,
      "name": "麻布"
    },
    {
      "id": 5,
      "name": "牛仔布"
    }
  ]
}

成功响应 - 带分页 (200 OK)

{
  "count": 50,
  "next": "http://localhost:8000/api/v1/products/quick/?limit=10&offset=10",
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "纯棉布料"
    },
    {
      "id": 2,
      "name": "涤纶布料"
    }
  ]
}

错误响应

状态码 说明
401 Unauthorized 未认证或 Token 无效
403 Forbidden 无权限访问
{
  "detail": "Authentication credentials were not provided."
}

使用场景

1. 前端下拉框

// React 示例
import { useState, useEffect } from 'react';

function ProductSelect() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchProducts = async () => {
      setLoading(true);
      const response = await fetch('/api/v1/products/quick/?limit=100', {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      });
      const data = await response.json();
      setProducts(data.results);
      setLoading(false);
    };

    fetchProducts();
  }, []);

  return (
    <select>
      {products.map(product => (
        <option key={product.id} value={product.id}>
          {product.name}
        </option>
      ))}
    </select>
  );
}

2. 自动完成Autocomplete

// Vue 3 + Element Plus 示例
<template>
  <el-autocomplete
    v-model="searchText"
    :fetch-suggestions="queryProducts"
    placeholder="搜索产品"
    @select="handleSelect"
  />
</template>

<script setup>
import { ref } from 'vue';

const searchText = ref('');

const queryProducts = async (queryString, cb) => {
  const url = queryString
    ? `/api/v1/products/quick/?search=${encodeURIComponent(queryString)}&limit=10`
    : '/api/v1/products/quick/?limit=10';
  
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  const data = await response.json();
  
  // 转换为 Element Plus 需要的格式
  const suggestions = data.results.map(p => ({
    value: p.name,
    id: p.id
  }));
  
  cb(suggestions);
};

const handleSelect = (item) => {
  console.log('选中产品:', item.id, item.value);
};
</script>

3. 搜索框实时搜索

// Vanilla JavaScript 示例
const searchInput = document.getElementById('product-search');
const resultsContainer = document.getElementById('search-results');

let debounceTimer;

searchInput.addEventListener('input', (e) => {
  clearTimeout(debounceTimer);
  
  debounceTimer = setTimeout(async () => {
    const searchText = e.target.value.trim();
    
    if (searchText.length < 2) {
      resultsContainer.innerHTML = '';
      return;
    }
    
    const response = await fetch(
      `/api/v1/products/quick/?search=${encodeURIComponent(searchText)}&limit=10`,
      {
        headers: {
          'Authorization': `Bearer ${token}`
        }
      }
    );
    
    const data = await response.json();
    
    // 渲染搜索结果
    resultsContainer.innerHTML = data.results.map(product => `
      <div class="search-result" data-id="${product.id}">
        ${product.name}
      </div>
    `).join('');
  }, 300); // 300ms 防抖
});

Python 使用示例

基础查询

import requests

BASE_URL = "http://localhost:8000"
TOKEN = "your_jwt_token"
headers = {"Authorization": f"Bearer {TOKEN}"}

# 获取所有产品
response = requests.get(f"{BASE_URL}/api/v1/products/quick/", headers=headers)
products = response.json()

print(f"共有 {products['count']} 个产品:")
for product in products['results']:
    print(f"  - {product['id']}: {product['name']}")

分页查询

def get_all_products(token):
    """获取所有产品(自动处理分页)"""
    BASE_URL = "http://localhost:8000"
    headers = {"Authorization": f"Bearer {token}"}
    
    all_products = []
    offset = 0
    limit = 50
    
    while True:
        response = requests.get(
            f"{BASE_URL}/api/v1/products/quick/?limit={limit}&offset={offset}",
            headers=headers
        )
        data = response.json()
        
        all_products.extend(data['results'])
        
        if data['next'] is None:
            break
        
        offset += limit
    
    return all_products

# 使用
products = get_all_products(token)
print(f"总共获取了 {len(products)} 个产品")

搜索查询

def search_products(token, query, limit=10):
    """搜索产品"""
    BASE_URL = "http://localhost:8000"
    headers = {"Authorization": f"Bearer {token}"}
    
    response = requests.get(
        f"{BASE_URL}/api/v1/products/quick/?search={query}&limit={limit}",
        headers=headers
    )
    
    return response.json()['results']

# 使用
results = search_products(token, "布料", limit=5)
for product in results:
    print(f"{product['id']}: {product['name']}")

性能优化建议

前端缓存

// 使用本地缓存避免重复请求
class ProductCache {
  constructor() {
    this.cache = new Map();
    this.cacheTimeout = 5 * 60 * 1000; // 5分钟
  }

  async getProducts(search = '', limit = 100) {
    const cacheKey = `${search}:${limit}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return cached.data;
    }

    const url = search
      ? `/api/v1/products/quick/?search=${encodeURIComponent(search)}&limit=${limit}`
      : `/api/v1/products/quick/?limit=${limit}`;

    const response = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    const data = await response.json();

    this.cache.set(cacheKey, {
      data: data.results,
      timestamp: Date.now()
    });

    return data.results;
  }

  clear() {
    this.cache.clear();
  }
}

const productCache = new ProductCache();

防抖搜索

// 防抖函数避免频繁请求
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const searchProducts = debounce(async (query) => {
  const response = await fetch(
    `/api/v1/products/quick/?search=${encodeURIComponent(query)}&limit=10`,
    {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    }
  );
  const data = await response.json();
  updateSearchResults(data.results);
}, 300);

常见问题

Q: 为什么只返回 id 和 name

A: 这是一个轻量级接口,专门为下拉框和自动完成设计。只返回必要的字段可以:

  • 减少数据传输量
  • 提高响应速度
  • 降低服务器负载

如果需要完整的产品信息,请使用完整的产品 API。

Q: 搜索是否区分大小写?

A: 不区分。搜索使用 icontains 查询,对大小写不敏感。

Q: 最多能返回多少条数据?

A: 建议使用 limit 参数限制返回数量。对于下拉框,建议 limit=100;对于自动完成,建议 limit=10

Q: 如何实现"加载更多"功能?

A: 使用 offset 参数进行分页。每次增加 offset 的值来获取下一页数据。

let offset = 0;
const limit = 20;

async function loadMore() {
  const response = await fetch(
    `/api/v1/products/quick/?limit=${limit}&offset=${offset}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );
  const data = await response.json();
  
  appendProducts(data.results);
  offset += limit;
  
  // 检查是否还有更多数据
  return data.next !== null;
}

测试

完整的测试套件位于 api_v1/views/products/test_products_api.py

运行测试:

# 运行所有产品 API 测试
python manage.py test api_v1.views.products

# 运行特定测试
python manage.py test api_v1.views.products.test_products_api.ProductQuickAPITestCase.test_search_products_by_name

测试覆盖:

  • 基础列表查询(带/不带分页)
  • 名称模糊搜索
  • 搜索 + 分页组合
  • 大小写不敏感搜索
  • 空结果处理
  • 认证检查
  • 响应格式验证
  • 边界情况测试

更新日志

2025-11-18

  • 初始版本发布
  • 支持产品列表查询(仅 id 和 name
  • 支持分页LimitOffset
  • 支持按名称模糊搜索
  • 完整的测试覆盖

相关链接