通用的业务列表字段可以动态调整的功能设计与实现

764 阅读5分钟

要实现一个通用的业务列表字段可以动态调整的功能,主要的目标是:让不同用户可以根据自己的需求,定制展示的字段以及字段的顺序。这可以通过以下步骤来实现:

image.png

功能要求:

  1. 字段选择与定制:用户可以选择在列表中显示哪些字段。
  2. 字段排序:用户可以拖动或调整字段的顺序。
  3. 保存设置:每个用户的字段选择和顺序需要保存,方便下次加载时使用。
  4. 灵活配置:支持不同类型的业务列表,字段可以是固定的(如客户名称、手机号等),也可以是动态的。

总体设计思路:

  1. 字段管理:每个业务列表(例如:客户列表)有一组预定义的字段,用户可以选择显示哪些字段,并调整这些字段的显示顺序。
  2. 用户个性化设置:每个用户的字段设置(显示与否、排序顺序)需要进行保存,这样在用户下次访问时,可以加载个性化的配置。
  3. 前端交互:用户通过前端界面选择需要显示的字段和调整字段顺序,操作通过API请求传递到后端。
  4. 后端存储:后端保存每个用户的字段设置,可以使用数据库保存字段配置或使用文件存储配置。

关键模块:

  1. 字段管理模块:负责定义所有字段。
  2. 用户设置模块:保存每个用户的字段显示设置。
  3. 接口模块:提供API给前端来获取、更新用户的字段显示设置。

1. 数据库设计

假设我们有一个"客户"列表,字段包括:客户名称、手机号、邮箱等。

  1. 字段定义表(用于定义字段)
    • fields:定义所有可能的字段。
CREATE TABLE fields (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,  -- 字段名称(如:客户名称、手机号等)
    display_name VARCHAR(255) NOT NULL, -- 用户展示的字段名称
    type VARCHAR(50) NOT NULL -- 字段类型,如:string、number、date等
);
  1. 用户字段设置表(保存用户个性化设置)
    • user_field_settings:保存每个用户选择的字段和排序顺序。
CREATE TABLE user_field_settings (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,  -- 用户ID
    field_id INT NOT NULL, -- 字段ID(与fields表关联)
    display_order INT NOT NULL,  -- 字段展示顺序
    is_visible BOOLEAN NOT NULL DEFAULT TRUE, -- 是否显示该字段
    FOREIGN KEY (field_id) REFERENCES fields(id),
    UNIQUE(user_id, field_id)  -- 保证每个用户每个字段只有一条记录
);

2. 后端设计

后端主要实现以下几个功能:

  • 获取字段配置
  • 获取用户个性化字段设置
  • 更新用户个性化字段设置

2.1 结构体定义

我们需要一些结构体来表示字段信息和用户的设置。

package model

// Field 定义了一个字段
type Field struct {
	ID           int    `json:"id"`
	Name         string `json:"name"`           // 字段名称,例如 "customer_name"
	DisplayName  string `json:"display_name"`    // 展示的字段名称,例如 "客户名称"
	Type         string `json:"type"`           // 字段类型,例如 "string"
}

// UserFieldSetting 用户的字段设置
type UserFieldSetting struct {
	UserID      int  `json:"user_id"`
	FieldID     int  `json:"field_id"`
	DisplayOrder int  `json:"display_order"`
	IsVisible   bool `json:"is_visible"`
}

2.2 控制器与服务

我们需要编写API来获取和更新字段设置。

获取字段设置
// controller/field_controller.go

package controller

import (
	"net/http"
	"handover/service"
	"github.com/tal-tech/go-zero/rest"
	"github.com/tal-tech/go-zero/core/logx"
)

type FieldController struct {
	FieldService *service.FieldService
}

func (fc *FieldController) GetFieldsByUser(w http.ResponseWriter, r *http.Request) {
	// 获取当前用户ID
	userID := r.Header.Get("User-ID")  // 假设用户ID从header中获取

	fields, err := fc.FieldService.GetUserFields(userID)
	if err != nil {
		logx.Error("Error getting user fields: ", err)
		http.Error(w, "Failed to get fields", http.StatusInternalServerError)
		return
	}

	// 返回字段数据
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(fields)
}
更新用户字段设置
// controller/field_controller.go

func (fc *FieldController) UpdateUserFields(w http.ResponseWriter, r *http.Request) {
	// 解析请求体中的字段设置
	var settings []model.UserFieldSetting
	err := json.NewDecoder(r.Body).Decode(&settings)
	if err != nil {
		logx.Error("Error parsing request body: ", err)
		http.Error(w, "Invalid input", http.StatusBadRequest)
		return
	}

	// 获取当前用户ID
	userID := r.Header.Get("User-ID")

	// 更新字段设置
	err = fc.FieldService.UpdateUserFields(userID, settings)
	if err != nil {
		logx.Error("Error updating user fields: ", err)
		http.Error(w, "Failed to update fields", http.StatusInternalServerError)
		return
	}

	// 返回成功响应
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Field settings updated successfully"))
}

2.3 服务层

// service/field_service.go

package service

import (
	"handover/store"
	"handover/model"
)

type FieldService struct {
	Store *store.Store
}

// 获取用户字段设置
func (fs *FieldService) GetUserFields(userID string) ([]model.Field, error) {
	// 查询数据库,获取用户的字段设置
	fields, err := fs.Store.GetFieldsByUserID(userID)
	if err != nil {
		return nil, err
	}
	return fields, nil
}

// 更新用户字段设置
func (fs *FieldService) UpdateUserFields(userID string, settings []model.UserFieldSetting) error {
	// 更新数据库,保存用户的字段设置
	err := fs.Store.SaveUserFieldSettings(userID, settings)
	if err != nil {
		return err
	}
	return nil
}

2.4 数据访问层

// store/store.go

package store

import (
	"handover/model"
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)

type Store struct {
	Conn *sql.DB
}

// 获取用户的字段配置
func (s *Store) GetFieldsByUserID(userID string) ([]model.Field, error) {
	// SQL查询获取用户字段设置
	rows, err := s.Conn.Query(`
		SELECT f.id, f.name, f.display_name, f.type
		FROM fields f
		JOIN user_field_settings ufs ON f.id = ufs.field_id
		WHERE ufs.user_id = ? AND ufs.is_visible = TRUE
		ORDER BY ufs.display_order`, userID)

	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var fields []model.Field
	for rows.Next() {
		var field model.Field
		if err := rows.Scan(&field.ID, &field.Name, &field.DisplayName, &field.Type); err != nil {
			return nil, err
		}
		fields = append(fields, field)
	}
	return fields, nil
}

// 保存用户的字段设置
func (s *Store) SaveUserFieldSettings(userID string, settings []model.UserFieldSetting) error {
	tx, err := s.Conn.Begin()
	if err != nil {
		return err
	}

	// 删除当前用户的字段设置
	_, err = tx.Exec("DELETE FROM user_field_settings WHERE user_id = ?", userID)
	if err != nil {
		tx.Rollback()
		return err
	}

	// 插入新的字段设置
	for _, setting := range settings {
		_, err = tx.Exec("INSERT INTO user_field_settings (user_id, field_id, display_order, is_visible) VALUES (?, ?, ?, ?)",
			userID, setting.FieldID, setting.DisplayOrder, setting.IsVisible)
		if err != nil {
			tx.Rollback()
			return err
		}
	}

	return tx.Commit()
}

3. 前端设计

前端需要提供以下功能:

  1. 字段选择:用户可以选择显示哪些字段,可以通过多选框或者拖拽排序的方式来实现。
  2. 保存设置:用户选择完成后,可以点击保存按钮,调用API保存设置。
  3. 显示设置:当用户打开列表时,调用API加载保存的字段设置,并根据字段顺序和是否可见来动态展示列表。

前端的具体实现可能需要使用一些UI组件库来支持拖拽排序和字段选择功能。常用的库有:React DnD、Ant Design的Table组件等