diff --git a/fs_gen_api.sh b/fs_gen_api.sh
index 6d35a29e..a699c2fc 100755
--- a/fs_gen_api.sh
+++ b/fs_gen_api.sh
@@ -1,5 +1,14 @@
 #! /bin/bash
 name=${1%%\\*} 
+
+options=("backend")
+for option in "${options[@]}"; do
+    if [ "$name" = "$option" ]; then 
+        echo "不能在"$name"里使用"
+        exit
+    fi 
+done
+
 echo $name
 goctl api go -api server_api/$name.api -dir server/$name --home ./goctl_template/
 # ctxName=server/$name/internal/svc/servicecontext.go
diff --git a/goctl_template_backend/api/handler.tpl b/goctl_template_backend/api/handler.tpl
index 5bd709a9..7ca8f282 100644
--- a/goctl_template_backend/api/handler.tpl
+++ b/goctl_template_backend/api/handler.tpl
@@ -20,7 +20,7 @@ func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
 			// 定义错误变量
 			err error
 			// 定义用户信息变量
-			userinfo *auth.UserInfo
+			userinfo *auth.BackendUserInfo
 		)
 		// 解析JWT token,并对空用户进行判断
 		claims, err := svcCtx.ParseJwtToken(r)
diff --git a/model/gmodel/fs_canteen_product_logic.go b/model/gmodel/fs_canteen_product_logic.go
index 0795284f..44dc16f6 100755
--- a/model/gmodel/fs_canteen_product_logic.go
+++ b/model/gmodel/fs_canteen_product_logic.go
@@ -27,8 +27,5 @@ func (c *FsCanteenProductModel) Create(ctx context.Context, data *FsCanteenProdu
 
 func (c *FsCanteenProductModel) GetAllByCanteenTypeIdOrderAsc(ctx context.Context, typeId int64) (resp []FsCanteenProduct, err error) {
 	err = c.db.WithContext(ctx).Model(&FsCanteenProduct{}).Where("`canteen_type` = ? and `status` = ?", typeId, 1).Order("sort asc").Find(&resp).Error
-	if err != nil {
-		return nil, err
-	}
-	return
+	return resp, err
 }
diff --git a/model/gmodel/fs_product_logic.go b/model/gmodel/fs_product_logic.go
index b162d634..255a2e82 100755
--- a/model/gmodel/fs_product_logic.go
+++ b/model/gmodel/fs_product_logic.go
@@ -49,3 +49,8 @@ func (p *FsProductModel) GetRandomProductList(ctx context.Context, limit int) (r
 	}
 	return
 }
+
+func (p *FsProductModel) FindAllOnlyByIds(ctx context.Context, ids []int64) (resp []*FsProduct, err error) {
+	err = p.db.WithContext(ctx).Model(&FsProduct{}).Where("`id` IN (?)", ids).Find(&resp).Error
+	return resp, err
+}
diff --git a/model/gmodel/fs_product_price_logic.go b/model/gmodel/fs_product_price_logic.go
index f890b324..6a299b56 100755
--- a/model/gmodel/fs_product_price_logic.go
+++ b/model/gmodel/fs_product_price_logic.go
@@ -71,3 +71,20 @@ func (p *FsProductPriceModel) GetPriceListByProductIds(ctx context.Context, prod
 	}
 	return
 }
+
+// 产品价格
+type ProductPrice struct {
+	Id         int64       `json:"id"`
+	MinBuyNum  int64       `json:"min_buy_num"`
+	StepNum    interface{} `json:"step_num"`
+	StepPrice  interface{} `json:"step_price"`
+	ProductId  int64       `json:"product_id"`
+	SizeId     int64       `json:"size_id"`
+	EachBoxNum int64       `json:"each_box_num"`
+}
+
+func (c *FsProductPriceModel) GetAllSelectBySizeId(ctx context.Context, sizeIds []int64) (prices []*ProductPrice, err error) {
+
+	err = c.db.WithContext(ctx).Model(&ProductPrice{}).Where("size_id IN (?) AND status = ?", sizeIds, 1).Select("id, min_buy_num, step_num, step_price, product_id, size_id, each_box_num").Find(&prices).Error
+	return prices, err
+}
diff --git a/model/gmodel/fs_product_size_logic.go b/model/gmodel/fs_product_size_logic.go
index aa8576f1..bf008f69 100755
--- a/model/gmodel/fs_product_size_logic.go
+++ b/model/gmodel/fs_product_size_logic.go
@@ -45,3 +45,13 @@ func (s *FsProductSizeModel) GetAllByProductIds(ctx context.Context, productIds
 	}
 	return
 }
+
+type CapacityId struct {
+	Id       int64  `json:"id"`
+	Capacity string `json:"capacity"`
+}
+
+func (c *FsProductSizeModel) GetAllSelectIdAndCapacityByIds(ctx context.Context, sizeIds []int64) (sizes []CapacityId, err error) {
+	err = c.db.WithContext(ctx).Where("id IN ?", sizeIds).Select("id, capacity").Find(&sizes).Error
+	return sizes, err
+}
diff --git a/model/gmodel/fs_quotation_logic.go b/model/gmodel/fs_quotation_logic.go
index e68225aa..27a9fbdc 100644
--- a/model/gmodel/fs_quotation_logic.go
+++ b/model/gmodel/fs_quotation_logic.go
@@ -1,2 +1,10 @@
 package gmodel
-// TODO: 使用model的属性做你想做的
\ No newline at end of file
+
+import "context"
+
+// TODO: 使用model的属性做你想做的
+
+func (m *FsQuotationModel) FindOneOnlyById(ctx context.Context, quotationId int64) (resp FsQuotation, err error) {
+	err = m.db.WithContext(ctx).Model(&resp).Where("`quotation_id` = ?", quotationId).Take(&resp).Error
+	return resp, err
+}
diff --git a/model/gmodel/fs_quotation_product_logic.go b/model/gmodel/fs_quotation_product_logic.go
index e68225aa..a230f11f 100644
--- a/model/gmodel/fs_quotation_product_logic.go
+++ b/model/gmodel/fs_quotation_product_logic.go
@@ -1,2 +1,10 @@
 package gmodel
-// TODO: 使用model的属性做你想做的
\ No newline at end of file
+
+import "context"
+
+// TODO: 使用model的属性做你想做的
+
+func (c *FsQuotationProductModel) GetAllByQuotationIdOrderAsc(ctx context.Context, quotationId int64) (resp []FsQuotationProduct, err error) {
+	err = c.db.WithContext(ctx).Model(&FsQuotationProduct{}).Where("`quotation_id` = ? and `status` = ?", quotationId, 1).Order("sort asc").Find(&resp).Error
+	return resp, err
+}
diff --git a/model/gmodel/fs_quotation_remark_template_logic.go b/model/gmodel/fs_quotation_remark_template_logic.go
index e68225aa..fff0c296 100644
--- a/model/gmodel/fs_quotation_remark_template_logic.go
+++ b/model/gmodel/fs_quotation_remark_template_logic.go
@@ -1,2 +1,10 @@
 package gmodel
-// TODO: 使用model的属性做你想做的
\ No newline at end of file
+
+import "context"
+
+// TODO: 使用model的属性做你想做的
+
+func (c *FsQuotationRemarkTemplateModel) GetAllSelectContent(ctx context.Context) (content []string, err error) {
+	err = c.db.WithContext(ctx).Model(&FsQuotationRemarkTemplate{}).Where("status = ?", 1).Select("content").Find(&content).Error
+	return content, err
+}
diff --git a/server/backend/internal/handler/quotationdetailhandler.go b/server/backend/internal/handler/quotationdetailhandler.go
index 6585222e..59640c7b 100644
--- a/server/backend/internal/handler/quotationdetailhandler.go
+++ b/server/backend/internal/handler/quotationdetailhandler.go
@@ -48,7 +48,7 @@ func QuotationDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 			return
 		}
 
-		var req types.Request
+		var req types.RequestQuotationId
 		// 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据
 		if err := httpx.Parse(r, &req); err != nil {
 			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
diff --git a/server/backend/internal/logic/quotationdetaillogic.go b/server/backend/internal/logic/quotationdetaillogic.go
index 8eb3b01b..17cf4ee6 100644
--- a/server/backend/internal/logic/quotationdetaillogic.go
+++ b/server/backend/internal/logic/quotationdetaillogic.go
@@ -1,15 +1,20 @@
 package logic
 
 import (
+	"encoding/json"
 	"fusenapi/utils/auth"
 	"fusenapi/utils/basic"
+	"fusenapi/utils/collect"
+	"strings"
 
 	"context"
 
 	"fusenapi/server/backend/internal/svc"
 	"fusenapi/server/backend/internal/types"
 
+	"github.com/tidwall/gjson"
 	"github.com/zeromicro/go-zero/core/logx"
+	"gorm.io/gorm"
 )
 
 type QuotationDetailLogic struct {
@@ -26,9 +31,193 @@ func NewQuotationDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Q
 	}
 }
 
-func (l *QuotationDetailLogic) QuotationDetail(req *types.Request, userinfo *auth.BackendUserInfo) (resp *basic.Response) {
+func GetPrice(num int64, stepNum []int64, stepPrice []int64) int64 {
+	//阶梯价计算
+	if num > stepNum[len(stepNum)-1] {
+		return stepPrice[len(stepPrice)-1]
+	}
+	for k, v := range stepNum {
+		if num <= v {
+			return stepPrice[k]
+		}
+	}
+	return 0
+}
+
+func (l *QuotationDetailLogic) QuotationDetail(req *types.RequestQuotationId, userinfo *auth.BackendUserInfo) (resp *basic.Response) {
 	// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
 	// userinfo 传入值时, 一定不为null
+	quot, err := l.svcCtx.AllModels.FsQuotation.FindOneOnlyById(l.ctx, req.QuotationId)
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return resp.SetStatus(basic.CodeDbRecordNotFoundErr)
+		}
+		return resp.SetStatus(basic.CodeDbSqlErr)
+	}
+
+	CanteenProduct, err := l.svcCtx.AllModels.FsCanteenProduct.GetAllByCanteenTypeIdOrderAsc(l.ctx, *quot.CanteenType)
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return resp.SetStatus(basic.CodeDbRecordNotFoundErr)
+		}
+		return resp.SetStatus(basic.CodeDbSqlErr)
+	}
+
+	quotation, err := l.svcCtx.AllModels.FsQuotationProduct.GetAllByQuotationIdOrderAsc(l.ctx, req.QuotationId)
+	if err != nil {
+		if err != gorm.ErrRecordNotFound {
+			return resp.SetStatus(basic.CodeDbSqlErr)
+		}
+	}
+
+	var target any = quotation
+	if len(quotation) == 0 {
+		target = CanteenProduct
+	}
+
+	jdata, err := json.Marshal(target)
+	if err != nil {
+		logx.Error(err)
+		return resp.SetStatus(basic.CodeJsonErr)
+	}
+
+	list := gjson.ParseBytes(jdata)
+
+	qFlag := len(quotation) > 0
+
+	//获取备注模板
+	markList, err := l.svcCtx.AllModels.FsQuotationRemarkTemplate.GetAllSelectContent(l.ctx)
+
+	var productIds = collect.ArrayColumn[int64](CanteenProduct, "product_id")
+	productList, err := l.svcCtx.AllModels.FsProduct.FindAllOnlyByIds(l.ctx, productIds)
+	collect.Array2MapByKeyTag[int64](productList, "id")
+
+	//获取size信息
+	var sizeIds = collect.ArrayColumn[int64](CanteenProduct, "SizeId")
+	sizes, err := l.svcCtx.AllModels.FsProductSize.GetAllSelectIdAndCapacityByIds(l.ctx, sizeIds)
+
+	//获取价格信息
+	productPrice, err := l.svcCtx.AllModels.FsProductPrice.GetAllSelectBySizeId(l.ctx, sizeIds)
+	// product := []map[string]interface{}{}
+
+	product := []map[string]interface{}{}
+	for _, parr := range list.Array() {
+
+		var priceList []map[string]int64
+
+		if !qFlag {
+
+			if price, ok := productPrice[parr.Get("size_id").Int()]; !ok {
+				priceList = []map[string]int64{
+					{"num": 1, "price": 0},
+					{"num": 1, "price": 0},
+					{"num": 1, "price": 0},
+				}
+			} else {
+
+				i := 0
+				price.StepNum = collect.ArrayString2Int(strings.Split(price.StepNum.(string), ","))
+				price.StepPrice = collect.ArrayString2Int(strings.Split(price.StepPrice.(string), ","))
+				for price.MinBuyNum < int64(len(price.StepNum.([]string))+5) && i < 3 {
+					priceList = append(priceList, map[string]int64{
+						"num":   int64(price.MinBuyNum * price.EachBoxNum),
+						"price": GetPrice(price.MinBuyNum, price.StepNum.([]int64), price.StepPrice.([]int64)) / 100,
+					})
+					price.MinBuyNum++
+					i++
+				}
+				if len(priceList) < 3 {
+					for j := 0; j < 3-len(priceList); j++ {
+						priceList = append(priceList, map[string]int64{"num": 1, "price": 0})
+					}
+				}
+			}
+		}
+
+ 
+
+		var (
+			Id        any
+			priceInfo any
+			mark      any
+			sid       any
+
+			isGift bool
+			name   any
+			size   any
+			cycle  any
+			img    any
+
+			num any
+		)
+		if qFlag {
+
+			Id = parr.Get("id")
+
+			err = json.Unmarshal([]byte(parr.Get("price_info").String()), &priceInfo)
+			if err != nil {
+				logx.Error(err)
+				return resp.SetStatus(basic.CodeJsonErr)
+			}
+
+			if parr.Get("sid").Exists() {
+				sid = parr.Get("sid").Int()
+			}
+
+			name = parr.Get("name").String()
+			size = parr.Get("size").Int()
+			cycle = parr.Get("cycle").Int()
+			img = parr.Get("img").String()
+
+			if parr.Get("remark").Exists() {
+				err = json.Unmarshal([]byte(parr.Get("remark").String()), &mark)
+				if err != nil {
+					logx.Error(err)
+					return resp.SetStatus(basic.CodeJsonErr)
+				}
+			}
+
+			num = parr.Get("num").Int()
+
+		} else {
+
+			if parr.Get("sid").Exists() {
+				sid = parr.Get("sid").Int()
+			}
+
+			productList
+
+			$productList[$parr['product_id']]['title']
+		 
+
+		}
+
+		product = append(product, map[string]interface{}{
+			"id":         Id,
+			"s_id":       sid,
+			"is_gift":    isGift,
+			"name":       name,
+			"size":       size,
+			"cycle":      cycle,
+			"img":        img,
+			"mark":       mark,
+			"price_list": priceInfo,
+			"num":        num,
+		})
+
+		// product = append(product, map[string]interface{}{
+		// 	"id":      qFlag ? parr.ID : nil,
+		// 	"sId":     parr.SID ? parr.SID : nil,
+		// 	"isGift":  qFlag && parr.IsGift,
+		// 	"name":    qFlag ? parr.Name : productList[parr.ProductID].Title,
+		// 	"size":    qFlag ? parr.Size : sizes[parr.SizeId].Capacity,
+		// 	"cycle":   qFlag ? parr.Cycle : productList[parr.ProductID].ProduceDays + productList[parr.ProductID].DeliveryDays,
+		// 	"img":     qFlag ? parr.Img : "",
+		// 	"mark":    qFlag && parr.Remark ? json.Unmarshal(parr.Remark) : nil,
+		// 	"priceList": qFlag ? json.Unmarshal(parr.PriceInfo) : priceList,
+		// 	"num":     qFlag ? parr.Num : 1,
+		// })
+	}
 
 	return resp.SetStatus(basic.CodeOK)
 }
diff --git a/server/product-template/internal/handler/addbasemaphandler.go b/server/product-template/internal/handler/addbasemaphandler.go
new file mode 100644
index 00000000..a2c9c38e
--- /dev/null
+++ b/server/product-template/internal/handler/addbasemaphandler.go
@@ -0,0 +1,73 @@
+package handler
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"fusenapi/utils/auth"
+	"fusenapi/utils/basic"
+
+	"fusenapi/server/product-template/internal/logic"
+	"fusenapi/server/product-template/internal/svc"
+	"fusenapi/server/product-template/internal/types"
+)
+
+func AddBaseMapHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		var (
+			// 定义错误变量
+			err error
+			// 定义用户信息变量
+			userinfo *auth.BackendUserInfo
+		)
+		// 解析JWT token,并对空用户进行判断
+		claims, err := svcCtx.ParseJwtToken(r)
+		// 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil || claims == nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,            // 返回401状态码,表示未授权
+				Message: "unauthorized", // 返回未授权信息
+			})
+			logx.Info("unauthorized:", err.Error()) // 记录错误日志
+			return
+		}
+
+		// 从token中获取对应的用户信息
+		userinfo, err = auth.GetBackendUserInfoFormMapClaims(claims)
+		// 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,
+				Message: "unauthorized",
+			})
+			logx.Info("unauthorized:", err.Error())
+			return
+		}
+
+		var req types.AddBaseMapReq
+		// 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据
+		if err := httpx.Parse(r, &req); err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    510,
+				Message: "parameter error",
+			})
+			logx.Info(err)
+			return
+		}
+		// 创建一个业务逻辑层实例
+		l := logic.NewAddBaseMapLogic(r.Context(), svcCtx)
+		resp := l.AddBaseMap(&req, userinfo)
+		// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
+		if resp != nil {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		} else {
+			err := errors.New("server logic is error, resp must not be nil")
+			httpx.ErrorCtx(r.Context(), w, err)
+			logx.Error(err)
+		}
+	}
+}
diff --git a/server/product-template/internal/handler/getbasemaplisthandler.go b/server/product-template/internal/handler/getbasemaplisthandler.go
new file mode 100644
index 00000000..24d9f887
--- /dev/null
+++ b/server/product-template/internal/handler/getbasemaplisthandler.go
@@ -0,0 +1,61 @@
+package handler
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"fusenapi/utils/auth"
+	"fusenapi/utils/basic"
+
+	"fusenapi/server/product-template/internal/logic"
+	"fusenapi/server/product-template/internal/svc"
+)
+
+func GetBaseMapListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		var (
+			// 定义错误变量
+			err error
+			// 定义用户信息变量
+			userinfo *auth.BackendUserInfo
+		)
+		// 解析JWT token,并对空用户进行判断
+		claims, err := svcCtx.ParseJwtToken(r)
+		// 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil || claims == nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,            // 返回401状态码,表示未授权
+				Message: "unauthorized", // 返回未授权信息
+			})
+			logx.Info("unauthorized:", err.Error()) // 记录错误日志
+			return
+		}
+
+		// 从token中获取对应的用户信息
+		userinfo, err = auth.GetBackendUserInfoFormMapClaims(claims)
+		// 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,
+				Message: "unauthorized",
+			})
+			logx.Info("unauthorized:", err.Error())
+			return
+		}
+
+		l := logic.NewGetBaseMapListLogic(r.Context(), svcCtx)
+		resp := l.GetBaseMapList(userinfo)
+		// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
+		if resp != nil {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		} else {
+			err := errors.New("server logic is error, resp must not be nil")
+			httpx.ErrorCtx(r.Context(), w, err)
+			logx.Error(err)
+		}
+	}
+}
diff --git a/server/product-template/internal/handler/gettemplatevdetailhandler.go b/server/product-template/internal/handler/gettemplatevdetailhandler.go
new file mode 100644
index 00000000..85fc95f5
--- /dev/null
+++ b/server/product-template/internal/handler/gettemplatevdetailhandler.go
@@ -0,0 +1,73 @@
+package handler
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"fusenapi/utils/auth"
+	"fusenapi/utils/basic"
+
+	"fusenapi/server/product-template/internal/logic"
+	"fusenapi/server/product-template/internal/svc"
+	"fusenapi/server/product-template/internal/types"
+)
+
+func GetTemplatevDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		var (
+			// 定义错误变量
+			err error
+			// 定义用户信息变量
+			userinfo *auth.BackendUserInfo
+		)
+		// 解析JWT token,并对空用户进行判断
+		claims, err := svcCtx.ParseJwtToken(r)
+		// 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil || claims == nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,            // 返回401状态码,表示未授权
+				Message: "unauthorized", // 返回未授权信息
+			})
+			logx.Info("unauthorized:", err.Error()) // 记录错误日志
+			return
+		}
+
+		// 从token中获取对应的用户信息
+		userinfo, err = auth.GetBackendUserInfoFormMapClaims(claims)
+		// 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,
+				Message: "unauthorized",
+			})
+			logx.Info("unauthorized:", err.Error())
+			return
+		}
+
+		var req types.GetTemplatevDetailReq
+		// 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据
+		if err := httpx.Parse(r, &req); err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    510,
+				Message: "parameter error",
+			})
+			logx.Info(err)
+			return
+		}
+		// 创建一个业务逻辑层实例
+		l := logic.NewGetTemplatevDetailLogic(r.Context(), svcCtx)
+		resp := l.GetTemplatevDetail(&req, userinfo)
+		// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
+		if resp != nil {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		} else {
+			err := errors.New("server logic is error, resp must not be nil")
+			httpx.ErrorCtx(r.Context(), w, err)
+			logx.Error(err)
+		}
+	}
+}
diff --git a/server/product-template/internal/handler/routes.go b/server/product-template/internal/handler/routes.go
new file mode 100644
index 00000000..95802ac0
--- /dev/null
+++ b/server/product-template/internal/handler/routes.go
@@ -0,0 +1,42 @@
+// Code generated by goctl. DO NOT EDIT.
+package handler
+
+import (
+	"net/http"
+
+	"fusenapi/server/product-template/internal/svc"
+
+	"github.com/zeromicro/go-zero/rest"
+)
+
+func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
+	server.AddRoutes(
+		[]rest.Route{
+			{
+				Method:  http.MethodGet,
+				Path:    "/product-template/detail",
+				Handler: GetTemplatevDetailHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodGet,
+				Path:    "/product-template/base-map-list",
+				Handler: GetBaseMapListHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/product-template/base-map-update",
+				Handler: SaveBaseMapHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/product-template/base-map-add",
+				Handler: AddBaseMapHandler(serverCtx),
+			},
+			{
+				Method:  http.MethodPost,
+				Path:    "/product-template/update-template",
+				Handler: UpdateTemplateHandler(serverCtx),
+			},
+		},
+	)
+}
diff --git a/server/product-template/internal/handler/savebasemaphandler.go b/server/product-template/internal/handler/savebasemaphandler.go
new file mode 100644
index 00000000..3921742d
--- /dev/null
+++ b/server/product-template/internal/handler/savebasemaphandler.go
@@ -0,0 +1,61 @@
+package handler
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"fusenapi/utils/auth"
+	"fusenapi/utils/basic"
+
+	"fusenapi/server/product-template/internal/logic"
+	"fusenapi/server/product-template/internal/svc"
+)
+
+func SaveBaseMapHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		var (
+			// 定义错误变量
+			err error
+			// 定义用户信息变量
+			userinfo *auth.BackendUserInfo
+		)
+		// 解析JWT token,并对空用户进行判断
+		claims, err := svcCtx.ParseJwtToken(r)
+		// 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil || claims == nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,            // 返回401状态码,表示未授权
+				Message: "unauthorized", // 返回未授权信息
+			})
+			logx.Info("unauthorized:", err.Error()) // 记录错误日志
+			return
+		}
+
+		// 从token中获取对应的用户信息
+		userinfo, err = auth.GetBackendUserInfoFormMapClaims(claims)
+		// 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,
+				Message: "unauthorized",
+			})
+			logx.Info("unauthorized:", err.Error())
+			return
+		}
+
+		l := logic.NewSaveBaseMapLogic(r.Context(), svcCtx)
+		resp := l.SaveBaseMap(userinfo)
+		// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
+		if resp != nil {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		} else {
+			err := errors.New("server logic is error, resp must not be nil")
+			httpx.ErrorCtx(r.Context(), w, err)
+			logx.Error(err)
+		}
+	}
+}
diff --git a/server/product-template/internal/handler/updatetemplatehandler.go b/server/product-template/internal/handler/updatetemplatehandler.go
new file mode 100644
index 00000000..98855c20
--- /dev/null
+++ b/server/product-template/internal/handler/updatetemplatehandler.go
@@ -0,0 +1,73 @@
+package handler
+
+import (
+	"errors"
+	"net/http"
+
+	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"fusenapi/utils/auth"
+	"fusenapi/utils/basic"
+
+	"fusenapi/server/product-template/internal/logic"
+	"fusenapi/server/product-template/internal/svc"
+	"fusenapi/server/product-template/internal/types"
+)
+
+func UpdateTemplateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		var (
+			// 定义错误变量
+			err error
+			// 定义用户信息变量
+			userinfo *auth.BackendUserInfo
+		)
+		// 解析JWT token,并对空用户进行判断
+		claims, err := svcCtx.ParseJwtToken(r)
+		// 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil || claims == nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,            // 返回401状态码,表示未授权
+				Message: "unauthorized", // 返回未授权信息
+			})
+			logx.Info("unauthorized:", err.Error()) // 记录错误日志
+			return
+		}
+
+		// 从token中获取对应的用户信息
+		userinfo, err = auth.GetBackendUserInfoFormMapClaims(claims)
+		// 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息
+		if err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    401,
+				Message: "unauthorized",
+			})
+			logx.Info("unauthorized:", err.Error())
+			return
+		}
+
+		var req types.UpdateTemplateReq
+		// 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据
+		if err := httpx.Parse(r, &req); err != nil {
+			httpx.OkJsonCtx(r.Context(), w, &basic.Response{
+				Code:    510,
+				Message: "parameter error",
+			})
+			logx.Info(err)
+			return
+		}
+		// 创建一个业务逻辑层实例
+		l := logic.NewUpdateTemplateLogic(r.Context(), svcCtx)
+		resp := l.UpdateTemplate(&req, userinfo)
+		// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
+		if resp != nil {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		} else {
+			err := errors.New("server logic is error, resp must not be nil")
+			httpx.ErrorCtx(r.Context(), w, err)
+			logx.Error(err)
+		}
+	}
+}
diff --git a/server_api/backend.api b/server_api/backend.api
index 06c4f53e..35bd82b1 100644
--- a/server_api/backend.api
+++ b/server_api/backend.api
@@ -12,7 +12,7 @@ import "basic.api"
 service backend {
 	// 报价单详情
 	@handler QuotationDetailHandler
-	get /quotation/detail(request) returns (response);
+	get /quotation/detail(RequestQuotationId) returns (response);
 	
 	@handler BackendUserLoginHandler
 	post /backend-user/login(RequestUserLogin) returns (response);
diff --git a/utils/collect/collect.go b/utils/collect/collect.go
new file mode 100644
index 00000000..af25cb3a
--- /dev/null
+++ b/utils/collect/collect.go
@@ -0,0 +1,82 @@
+package collect
+
+import (
+	"reflect"
+	"strconv"
+)
+
+func ArrayColumn[R any, T any](arr []T, column string) []R {
+	var result []R
+	s := reflect.ValueOf(arr)
+
+	for i := 0; i < s.Len(); i++ {
+		e := s.Index(i)
+		k := e.FieldByName(column)
+		result = append(result, k.Interface().(R))
+	}
+
+	return result
+}
+
+func ArrayIndex[T any](arr []T, index int) (result T, ok bool) {
+	if index < len(arr) {
+		result = arr[index]
+		ok = true
+		return
+	}
+	ok = false
+	return
+}
+
+func ArrayString2Int(arr interface{}) (result []int64) {
+	for _, a := range arr.([]string) {
+		v, err := strconv.ParseInt(a, 10, 64)
+		if err != nil {
+			panic(err)
+		}
+		result = append(result, v)
+	}
+	return result
+}
+
+func Array2MapByKey[KEY comparable, VALUE any](arrSrc []VALUE, fieldName string) (result map[KEY]VALUE) {
+	result = make(map[KEY]VALUE)
+	arr := reflect.ValueOf(arrSrc)
+
+	for i := 0; i < arr.Len(); i++ {
+		srcv := arr.Index(i)
+		fv := srcv.Elem().FieldByName(fieldName)
+		k := fv.Interface().(KEY)
+		result[k] = srcv.Interface().(VALUE)
+	}
+
+	return result
+}
+
+func Array2MapByKeyTag[KEY comparable, VALUE any](arrSrc []VALUE, tag string) (result map[KEY]VALUE) {
+
+	arr := reflect.ValueOf(arrSrc)
+
+	if arr.Len() == 0 {
+		return result
+	}
+	result = make(map[KEY]VALUE)
+
+	eleType := arr.Index(0).Elem().Type()
+
+	for j := 0; j < eleType.NumField(); j++ {
+		if value, ok := eleType.Field(j).Tag.Lookup("json"); ok && value == tag {
+
+			for i := 0; i < arr.Len(); i++ {
+				srcv := arr.Index(i)
+				fv := srcv.Elem().Field(j)
+				k := fv.Interface().(KEY)
+				result[k] = srcv.Interface().(VALUE)
+			}
+
+			return
+		}
+	}
+
+	return result
+}
diff --git a/utils/collect/collect_test.go b/utils/collect/collect_test.go
new file mode 100644
index 00000000..a1ff745d
--- /dev/null
+++ b/utils/collect/collect_test.go
@@ -0,0 +1,32 @@
+package collect
+
+import (
+	"log"
+	"testing"
+)
+
+type ABC struct {
+	A int64       `json:"a"`
+	B string      `json:"b"`
+	C interface{} `json:"c"`
+}
+
+func TestArray2MapByKey(t *testing.T) {
+	var abcs []*ABC = []*ABC{
+		{1, "2", 3},
+		{3, "1", 2},
+	}
+	a := Array2MapByKey[string](abcs, "B")
+	log.Printf("%##v", a)
+	log.Println(len(a))
+}
+
+func TestArray2MapByKeyTag(t *testing.T) {
+	var abcs []*ABC = []*ABC{
+		{1, "2", 3},
+		{3, "1", 2},
+	}
+	a := Array2MapByKeyTag[int64](abcs, "a")
+	log.Printf("%##v", a)
+	log.Println(len(a))
+}