js生成mock数据的方法

在前端开发需要生成各类数据,每次手写很痛苦~参考以下方法,可以快速生成需要的数据。

/**
 * 通用 Mock 数据生成器
 * 支持多种数据类型和自定义生成规则
 */

/**
 * 
 * 
 * // main.js - 使用示例
    import mockDataGenerator, { 
    generateFromTemplate, 
    dataTemplates 
    } from './mockDataGenerator.js';

    // 示例1: 基本使用
    const basicData = mockDataGenerator({
    count: 5,
    fields: {
        name: { type: 'string', pattern: '项目 {id}' },
        value: { type: 'number', min: 10, max: 100 },
        active: { type: 'boolean' }
    }
    });



    // 示例2: 使用预定义模板
    const users = generateFromTemplate('user', {
    count: 3,
    fields: {
        role: { options: ['管理员', '用户', '游客'] }
    }
    });

        console.log('用户数据:', users);

    // 示例3: 自定义商品数据
    const products = mockDataGenerator({
    count: 8,
    startId: 100,
    fields: {
        title: (i, id) => `智能手机 ${id}`,
        price: { type: 'number', min: 1000, max: 5000 },
        brand: { 
        type: 'string', 
        options: ['Apple', 'Samsung', 'Huawei', 'Xiaomi'] 
        },
        specs: {
        type: 'object',
        properties: {
            storage: { type: 'string', options: ['64GB', '128GB', '256GB'] },
            color: { type: 'string', options: ['黑色', '白色', '蓝色'] },
            screen: { type: 'string', options: ['6.1寸', '6.7寸'] }
        }
        },
        images: {
        type: 'array',
        length: 3,
        itemPattern: `https://example.com/images/product_{id}_{index}.jpg`
        }
    },
    onItemCreate: (item) => {
        // 添加计算字段
        item.discountPrice = parseFloat((item.price * 0.9).toFixed(2));
        item.isExpensive = item.price > 3000;
        return item;
    }
    });



    // 示例4: 文章数据
    const articles = generateFromTemplate('article', {
    count: 4,
    onItemCreate: (article) => {
        // 根据阅读量设置热门标志
        article.isHot = article.readCount > 5000;
        return article;
    }
    });
 * 
 */
/**
 * 根据类型生成值
 * @param {string} type 数据类型
 * @param {Object} options 配置选项
 * @returns {*} 生成的值
 */
function generateByType(type, options = {}) {
  const { 
    i, 
    currentId, 
    item, 
    min = 0, 
    max = 100,
    pattern,
    prefix = '',
    suffix = '',
    options: valueOptions = []
  } = options;
  
  switch (type.toLowerCase()) {
    case 'number':
    case 'float':
      const numberValue = min + Math.random() * (max - min);
      return parseFloat(numberValue.toFixed(2));
    
    case 'integer':
    case 'int':
      return Math.floor(min + Math.random() * (max - min + 1));
    
    case 'boolean':
    case 'bool':
      return Math.random() > 0.5;
    
    case 'string':
      if (pattern) {
        // 支持模板字符串,如: "Item {id}" -> "Item 1"
        return pattern
          .replace(/{id}/g, currentId)
          .replace(/{index}/g, i)
          .replace(/{prefix}/g, prefix)
          .replace(/{suffix}/g, suffix);
      }
      if (valueOptions.length > 0) {
        return valueOptions[Math.floor(Math.random() * valueOptions.length)];
      }
      return `${prefix}${currentId}${suffix}`;
    
    case 'date':
      const daysAgo = Math.floor(Math.random() * 365);
      const date = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000);
      return date.toISOString().split('T')[0]; // 返回 YYYY-MM-DD 格式
    
    case 'datetime':
      return new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString();
    
    case 'image':
      if (pattern) {
        return pattern
          .replace(/{id}/g, currentId)
          .replace(/{index}/g, i)
          .replace(/{width}/g, options.width || 300)
          .replace(/{height}/g, options.height || 200);
      }
      return `https://picsum.photos/seed/${currentId}/${options.width || 300}/${options.height || 200}`;
    
    case 'email':
      return `user${currentId}@example.com`;
    
    case 'phone':
      return `1${Math.floor(1000000000 + Math.random() * 9000000000)}`;
    
    case 'color':
      return `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`;
    
    case 'array':
      const arrayLength = options.length || Math.floor(Math.random() * 5) + 1;
      return Array.from({ length: arrayLength }, (_, index) => 
        options.itemPattern 
          ? options.itemPattern.replace(/{index}/g, index).replace(/{id}/g, currentId)
          : `item_${currentId}_${index}`
      );
    
    case 'object':
      return options.properties ? 
        Object.keys(options.properties).reduce((obj, key) => {
          obj[key] = generateByType(options.properties[key].type, {
            ...options.properties[key],
            i,
            currentId,
            item
          });
          return obj;
        }, {}) : {};
    
    default:
      return null;
  }
}

/**
 * 处理字段配置
 * @param {*} fieldConfig 字段配置
 * @param {number} i 索引
 * @param {number} currentId 当前ID
 * @param {Object} item 当前项目
 * @returns {*} 字段值
 */
function processFieldConfig(fieldConfig, i, currentId, item) {
  if (typeof fieldConfig === 'function') {
    return fieldConfig(i, currentId, item);
  }
  
  if (typeof fieldConfig === 'object' && fieldConfig !== null) {
    const { 
      type = 'string', 
      value, 
      options, 
      min, 
      max, 
      generator,
      ...rest 
    } = fieldConfig;
    
    if (value !== undefined) {
      return value;
    }
    
    if (generator) {
      return generator(i, currentId, item);
    }
    
    if (options && Array.isArray(options)) {
      return options[Math.floor(Math.random() * options.length)];
    }
    
    return generateByType(type, { 
      i, 
      currentId, 
      item, 
      min, 
      max, 
      options,
      ...rest 
    });
  }
  
  return fieldConfig;
}

/**
 * 主生成函数
 * @param {Object} config 配置对象
 * @returns {Array} 生成的模拟数据数组
 */
function mockDataGenerator(config = {}) {
  const {
    count = 20,
    startId = 1,
    fields = {},
    onItemCreate = null,
    unique = true
  } = config;

  const data = [];
  const usedIds = new Set();

  for (let i = 0; i < count; i++) {
    let currentId = startId + i;
    
    // 确保ID唯一
    if (unique) {
      while (usedIds.has(currentId)) {
        currentId++;
      }
      usedIds.add(currentId);
    }

    const item = { id: currentId };

    // 为每个定义的字段生成值
    Object.keys(fields).forEach(fieldName => {
      try {
        item[fieldName] = processFieldConfig(fields[fieldName], i, currentId, item);
      } catch (error) {
        console.warn(`生成字段 "${fieldName}" 时出错:`, error);
        item[fieldName] = null;
      }
    });

    // 执行回调
    let finalItem = item;
    if (onItemCreate) {
      try {
        const result = onItemCreate(item, i, currentId);
        if (result !== undefined && result !== null) {
          finalItem = result;
        }
      } catch (error) {
        console.warn('onItemCreate 回调执行出错:', error);
      }
    }

    data.push(finalItem);
  }

  return data;
}

/**
 * 预定义的数据模板
 */
const dataTemplates = {
  // 用户数据模板
  user: (overrides = {}) => ({
    count: 10,
    startId: 1,
    fields: {
      username: { type: 'string', pattern: 'user_{id}' },
      email: { type: 'email' },
      firstName: { type: 'string', options: ['张', '李', '王', '刘', '陈'] },
      lastName: { type: 'string', options: ['三', '四', '五', '六', '七'] },
      age: { type: 'integer', min: 18, max: 60 },
      phone: { type: 'phone' },
      avatar: { type: 'image', width: 100, height: 100 },
      isActive: { type: 'boolean' },
      createdAt: { type: 'datetime' },
      ...overrides.fields
    },
    ...overrides
  }),

  // 商品数据模板
  product: (overrides = {}) => ({
    count: 20,
    startId: 1,
    fields: {
      name: { type: 'string', pattern: '商品 {id}' },
      description: { type: 'string', pattern: '这是第 {id} 个商品的描述' },
      price: { type: 'number', min: 10, max: 1000 },
      originalPrice: (i, id) => {
        const price = 10 + Math.random() * 1000;
        return parseFloat((price * (1 + Math.random() * 0.5)).toFixed(2));
      },
      stock: { type: 'integer', min: 0, max: 100 },
      image: { type: 'image', width: 300, height: 200 },
      category: { 
        type: 'string', 
        options: ['电子产品', '服装', '食品', '家居', '图书'] 
      },
      tags: {
        type: 'array',
        length: 3,
        itemPattern: 'tag_{index}'
      },
      isAvailable: { type: 'boolean' },
      rating: { type: 'number', min: 1, max: 5 },
      reviewCount: { type: 'integer', min: 0, max: 1000 },
      ...overrides.fields
    },
    ...overrides
  }),

  // 文章数据模板
  article: (overrides = {}) => ({
    count: 15,
    startId: 1,
    fields: {
      title: { type: 'string', pattern: '文章标题 {id}' },
      content: { type: 'string', pattern: '这是第 {id} 篇文章的内容...' },
      author: { type: 'string', options: ['作者A', '作者B', '作者C'] },
      publishDate: { type: 'date' },
      readCount: { type: 'integer', min: 0, max: 10000 },
      likeCount: { type: 'integer', min: 0, max: 500 },
      category: { 
        type: 'string', 
        options: ['技术', '生活', '旅游', '美食', '财经'] 
      },
      tags: {
        type: 'array',
        options: ['热门', '推荐', '原创', '转载'],
        length: 2
      },
      isPublished: { type: 'boolean' },
      ...overrides.fields
    },
    ...overrides
  })
};

/**
 * 使用模板生成数据
 * @param {string} templateName 模板名称
 * @param {Object} overrides 覆盖配置
 * @returns {Array} 生成的数据
 */
function generateFromTemplate(templateName, overrides = {}) {
  const template = dataTemplates[templateName];
  if (!template) {
    throw new Error(`模板 "${templateName}" 不存在。可用模板: ${Object.keys(dataTemplates).join(', ')}`);
  }
  
  const config = typeof template === 'function' ? template(overrides) : template;
  return mockDataGenerator(config);
}

// 导出内容
export {
  mockDataGenerator,
  generateByType,
  processFieldConfig,
  generateFromTemplate,
  dataTemplates
};

// 默认导出
export default mockDataGenerator;

Comments

No comments yet. Why don’t you start the discussion?

发表回复