[golang gin框架] 20.Gin 商城项目-商品模块功能

一.商品模块数据表ER图关系分析

商品模块数据表相关功能关系见: [golang gin框架] 16.Gin 商城项目-商品模块数据表ER图关系分析

二.商品相关界面展示

商品列表

该商品列表有如下功能
1.增加商品按钮:跳转到增加商品页面
2.搜索功能:输入商品名称,点击搜索
3.修改商品字段(上架,精品,新平,热销)状态
4.修改排序数字
5.修改操作:点击修改跳转到修改页面
6.删除操作
7.分页操作
[golang gin框架] 20.Gin 商城项目-商品模块功能_第1张图片

添加商品

通用信息

功能如下:
1.输入商品相关信息
2.选择商品所属分类(从商品分类表goods_cate中选择)
3.上传商品logo图片
4.选择商品状态(单选)
5.选择商品(新品,热销,精品:复选框)
[golang gin框架] 20.Gin 商城项目-商品模块功能_第2张图片
[golang gin框架] 20.Gin 商城项目-商品模块功能_第3张图片

详细描述

功能:
1.引入富文本框,输入商品内容
2.上传商品相关图片到富文本
注意:这里图片上传后台要判断是否上传到云服务器
[golang gin框架] 20.Gin 商城项目-商品模块功能_第4张图片

商品属性

功能:
1.选择商品颜色(多选):从商品颜色表good_color中选择
2.输入商品其他相关属性
[golang gin框架] 20.Gin 商城项目-商品模块功能_第5张图片

规格与包装

功能:
1.选择商品类型:从商品类型表goods_type中选择
2.根据选择的商品类型,展示不同的商品类型属性:从商品类型属性表goods_type_attribute中选择
3.填写商品类型属性
[golang gin框架] 20.Gin 商城项目-商品模块功能_第6张图片

商品相册

功能:
1.选择商品相册,上传商品图片
注意:这里商品上传图片时,需要一个批量上传图片的插件,以及上传到云服务器还是本地服务器的判断
[golang gin框架] 20.Gin 商城项目-商品模块功能_第7张图片
[golang gin框架] 20.Gin 商城项目-商品模块功能_第8张图片
[golang gin框架] 20.Gin 商城项目-商品模块功能_第9张图片
总结:
1.用户点击添加商品按钮,进入添加商品页面
2.根据商品相关功能,布局商品页面
3.进入到通用信息页面,填写通用信息(一些基本的信息,以及选择商品分类,上传图片,单选,复选信息)
4.切换到详情描述,增加商品内容:引入富文本框,以及上传图片内容
5.切换到商品属性,填写相关属性以及选择颜色(复选框)
6.切换到规格与包装,选择商品类型,以及展示对应的商品类型属性,根据实际展示填写
7.切换到商品相册,选择上传图片,并上传
8.以上操作完成后,提交

修改商品

修改操作步骤和上面添加商品操作类似,只不过需要展示商品已有数据,然后进行处理,这里不一一介绍

删除商品

[golang gin框架] 20.Gin 商城项目-商品模块功能_第10张图片

修改属性状态,排序等

点击要修改的属性,进行状态修改
双击排序,生成焦点,修改数字,失去焦点,排序完成
该功能见: [golang gin框架] 15.Gin 商城项目-封装上传图片方法,轮播图的增删改查以及异步修改状态,数量
[golang gin框架] 20.Gin 商城项目-商品模块功能_第11张图片

三.代码展示

商品相关数据表见 [golang gin框架] 16.Gin 商城项目-商品模块数据表ER图关系分析
  1. tools.go工具类

项目所用到的工具类在models/tool.go下,代码如下:
package models

import (
    "context"
    "crypto/md5"
    "errors"
    "fmt"
    "github.com/aliyun/aliyun-oss-go-sdk/oss"
    "github.com/tencentyun/cos-go-sdk-v5"
    "github.com/gin-gonic/gin"
    "gopkg.in/ini.v1"
    "html/template"
    "io"
    "mime/multipart"
    "net/http"
    "net/url"
    "os"
    "path"
    "reflect"
    "strconv"
    "strings"
    "time"
    //引入模块的时候前面加个.表示可以直接使用模块里面的方法,无需加模块名称
    . "github.com/hunterhug/go_image"
)

//时间戳转换成日期函数
func UnixToTime(timestamp int) string {
    t := time.Unix(int64(timestamp), 0)
    return t.Format("2006-01-02 15:04:05")
}

//日期转换成时间戳
func DateToUnix(str string) int64 {
    template := "2006-01-02 15:04:05"
    t, err := time.ParseInLocation(template, str, time.Local)
    if err != nil {
        return 0
    }
    return t.Unix()
}

//获取当前时间戳(毫秒)
func GetUnix() int64 {
    return time.Now().Unix()
}

//获取当前时间戳(纳秒)
func GetUnixNano() int64 {
    return time.Now().UnixNano()
}

//获取当前日期
func GetDate() string {
    template := "2006-01-02 15:04:05"
    return time.Now().Format(template)
}

//获取年月日
func GetDay() string {
    template := "20060102"
    return time.Now().Format(template)
}

//md5加密
func Md5(str string) string {
    //data := []byte(str)
    //return fmt.Sprintf("%x\n", md5.Sum(data))

    h := md5.New()
    io.WriteString(h, str)
    return fmt.Sprintf("%x", h.Sum(nil))
}

//把字符串解析成html
func Str2Html(str string) template.HTML {
    return template.HTML(str)
}

//表示把string字符串转换成int
func Int(str string) (int, error) {
    n, err := strconv.Atoi(str)
    return n, err
}

//表示把string字符串转换成Float
func Float(str string) (float64, error) {
    n, err := strconv.ParseFloat(str, 64)
    return n, err
}

//表示把int转换成string字符串
func String(n int) string {
    str := strconv.Itoa(n)
    return str
}

//通过列获取系统设置里面的值,columnName就是结构体的属性名称
func GetSettingFromColumn(columnName string) string {
    setting := Setting{}
    DB.First(&setting)
    //反射来获取
    v := reflect.ValueOf(setting)
    val := v.FieldByName(columnName).String()
    return val
}

//获取oss的状态:是否开启上传到云服务器
func GetOssStatus() int  {
    cfg, err := ini.Load("./conf/app.ini")
    if err != nil {
        fmt.Printf("Fail to read file: %v", err)
        os.Exit(1)
    }
    ossStatus := cfg.Section("oss").Key("status").String()
    status, _:= Int(ossStatus)
    return status
}

//格式化图片:判断是否开启了oss
func FormatImg(str string) string  {
    if GetOssStatus() == 1 {  // 开启了oss,则获取oss上的图片
        return GetSettingFromColumn("OssDomain") + str
    } else {
        return "/" + str  //获取本地图片
    }
}

//图片上传方法
func UploadImg(c *gin.Context, picName string) (string, error) {
    //判断是否开启oss
    if GetOssStatus() == 1 {
        //return OssUploadImg(c, picName)
        return CosUploadImg(c, picName)
    } else {
        return LocalUploadImg(c, picName)
    }
}

//图片上传:上传到cos
func CosUploadImg(c *gin.Context, picName string) (string, error) {
    //1.获取上传文件
    file, err := c.FormFile(picName)
    //判断上传文件上否存在
    if err != nil { //说明上传文件不存在
        return "", nil
    }
    //2.获取后缀名,判断后缀是否正确: .jpg,.png,.gif,.jpeg
    extName := path.Ext(file.Filename)
    //设置后缀map
    allowExtMap := map[string]bool{
        ".jpg":  true,
        ".png":  true,
        ".gif":  true,
        ".jpeg": true,
    }
    //判断后缀是否合法
    if _, ok := allowExtMap[extName]; !ok {
        return "", errors.New("文件后缀名不合法")
    }
    //3.创建图片保存目录 ./static/upload/20230203
    //获取日期
    day := GetDay()
    //拼接目录, 上传时,cos会自动创建对应文件目录
    dir := "./static/upload/" + day
    //4.生成文件名称和文件保存目录: models.GetUnixNano() 获取时间戳(int64) 纳秒:防止速度过快而上传图片失败; strconv.FormatInt() 把时间戳(int64)转换成字符串
    filename := strconv.FormatInt(GetUnixNano(), 10) + extName
    //5.执行上传
    dst := path.Join(dir, filename)
    //上传文件到指定目录
    _, err1 := CosUpload(file, dst)
    if err1 != nil {
        return "", err1
    }
    fmt.Println(dst)
    return dst, nil
}

//图片上传:上传到OSS
func OssUploadImg(c *gin.Context, picName string) (string, error) {
    //1.获取上传文件
    file, err := c.FormFile(picName)
    //判断上传文件上否存在
    if err != nil { //说明上传文件不存在
        return "", nil
    }
    //2.获取后缀名,判断后缀是否正确: .jpg,.png,.gif,.jpeg
    extName := path.Ext(file.Filename)
    //设置后缀map
    allowExtMap := map[string]bool{
        ".jpg":  true,
        ".png":  true,
        ".gif":  true,
        ".jpeg": true,
    }
    //判断后缀是否合法
    if _, ok := allowExtMap[extName]; !ok {
        return "", errors.New("文件后缀名不合法")
    }
    //3.创建图片保存目录 ./static/upload/20230203
    //获取日期
    day := GetDay()
    //拼接目录, 上传时,oss会自动创建对应文件目录
    dir := "./static/upload/" + day
    //4.生成文件名称和文件保存目录: models.GetUnixNano() 获取时间戳(int64) 纳秒:防止速度过快而上传图片失败; strconv.FormatInt() 把时间戳(int64)转换成字符串
    filename := strconv.FormatInt(GetUnixNano(), 10) + extName
    //5.执行上传
    dst := path.Join(dir, filename)

    //上传文件到指定目录
    OssUpload(file, dst)
    return dst, nil
}

//封装oss上传图片方法
func OssUpload(file *multipart.FileHeader, dst string) (string, error) {
    // 1.创建OSSClient实例。
    client, err := oss.New("oss-cn-beijing.aliyuncs.com", "xxx", "xxxKEe9")
    if err != nil {
        return "", err
    }
    // 2.获取存储空间。
    bucket, err := client.Bucket("beego")
    if err != nil {
        return "", err
    }

    // 3.读取本地文件: file.Open()返回的File最终的类型为:io.Reader, 这样下面的上传就可以用了
    src, err := file.Open()
    if err != nil {
        return "", err
    }
    defer src.Close()

    // 上传文件流 bucket.PutObjec方法第二个参数类型为io.Reader, src的类型为
    err = bucket.PutObject(dst, src)
    if err != nil {
        return "", err
    }
    return dst, nil
}

//封装cos上传图片方法
func CosUpload(file *multipart.FileHeader, dst string) (string, error) {
    // 存储桶名称,由 bucketname-appid 组成,appid 必须填入,可以在 COS 控制台查看存储桶名称。 https://console.cloud.tencent.com/cos5/bucket
    // 替换为用户的 region,存储桶 region 可以在 COS 控制台“存储桶概览”查看 https://console.cloud.tencent.com/ ,关于地域的详情见 https://cloud.tencent.com/document/product/436/6224 。
    // 1.创建CosClient实例。
    //获取cos相关配置
    cfg, err := ini.Load("./conf/app.ini")
    if err != nil {
        fmt.Printf("Fail to read file: %v", err)
        os.Exit(1)
    }
    rawURL := cfg.Section("cos").Key("rawURL").String()
    secretID := cfg.Section("cos").Key("secretID").String()
    scretKey := cfg.Section("cos").Key("scretKey").String()
    u, _ := url.Parse(rawURL)
    b := &cos.BaseURL{BucketURL: u}
    client := cos.NewClient(b, &http.Client{
        Transport: &cos.AuthorizationTransport{
            // 通过环境变量获取密钥
            // 环境变量 SECRETID 表示用户的 SecretId,
            //登录访问管理控制台查看密钥,https://console.cloud.tencent.com/cam/capi
            SecretID: secretID,  // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
            // 环境变量 SECRETKEY 表示用户的 SecretKey,
            //登录访问管理控制台查看密钥,https://console.cloud.tencent.com/cam/capi
            SecretKey: scretKey,  // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
        },
    })

    //对象在存储桶中的唯一标识
    key := dst
    //通过文件流上传对象
    // 3.读取本地文件: file.Open()返回的File最终的类型为:io.Reader, 这样下面的上传就可以用了
    fd, errOpen := file.Open()
    if errOpen != nil {
        return "", errOpen
    }
    defer fd.Close()

    _, err = client.Object.Put(context.Background(), key, fd, nil)
    if err != nil {
        return "", err
    }

    return dst, nil
}

//图片上传:上传到本地
func LocalUploadImg(c *gin.Context, picName string) (string, error) {
    //1.获取上传文件
    file, err := c.FormFile(picName)
    //判断上传文件上否存在
    if err != nil { //说明上传文件不存在
        return "", nil
    }
    //2.获取后缀名,判断后缀是否正确: .jpg,.png,.gif,.jpeg
    extName := path.Ext(file.Filename)
    //设置后缀map
    allowExtMap := map[string]bool{
        ".jpg":  true,
        ".png":  true,
        ".gif":  true,
        ".jpeg": true,
    }
    //判断后缀是否合法
    if _, ok := allowExtMap[extName]; !ok {
        return "", errors.New("文件后缀名不合法")
    }
    //3.创建图片保存目录 ./static/upload/20230203
    //获取日期
    day := GetDay()
    //拼接目录
    dir := "./static/upload/" + day
    //创建目录:MkdirAll 目录不存在,会一次性创建多层
    err = os.MkdirAll(dir, 0666)
    if err != nil {
        return "", err
    }
    //4.生成文件名称和文件保存目录: models.GetUnixNano() 获取时间戳(int64) 纳秒:防止速度过快而上传图片失败; strconv.FormatInt() 把时间戳(int64)转换成字符串
    filename := strconv.FormatInt(GetUnixNano(), 10) + extName
    //5.执行上传
    dst := path.Join(dir, filename)
    //上传文件到指定目录
    c.SaveUploadedFile(file, dst)
    return dst, nil
}

//生成商品缩略图
func ResizeGoodsImage(filename string) {
    //获取文件后缀名
    extname := path.Ext(filename)
    //获取缩略图尺寸
    thumbnailSizeSlice := strings.Split(GetSettingFromColumn("ThumbnailSize"), ",")
    //static/upload/tao_400.png
    //static/upload/tao_400.png_100x100.png
    //遍历尺寸,生成缩略图
    for i := 0; i < len(thumbnailSizeSlice); i++ {
        savepath := filename + "_" + thumbnailSizeSlice[i] + "x" + thumbnailSizeSlice[i] + extname
        w, _ := Int(thumbnailSizeSlice[i])
        //调用github.com/hunterhug/go_image中的方法ThumbnailF2F(),生成缩略图
        err := ThumbnailF2F(filename, savepath, w, w)
        if err != nil {
            fmt.Println(err) //写个日志模块  处理日志
        }
    }
}
  1. 配置ini

项目所用到的app.ini配置在conf/app.ini文件下
app_name        = app测试
# 错误级别: DEBUG,INFO,WARNING,ERROR,FATAL
log_level       = DEBUG
app_mode        = production
# 管理后台,特殊的权限排除地址
excludeAuthPath = /,/welcome,/loginOut

[mysql]
ip       = 127.0.0.1
port     = 3306
user     = root
password = 123456
database = ginshop

[redis]
ip       = 127.0.0.1
port     = 9376
database = 1

# 是否开启oss, 1 开启, 0 关闭, oss的配置需要在后台管理系统中配置
[oss]
status = 1

# cos 文件存储密钥相关
[cos]
rawURL   = http://xxx.cos.ap-beijing.myqcloud.com
secretID = AKxxxXCaxxx
scretKey = 2xxxAsJTxxx6qIxxx
bucket   = xxx-xxx-13xxx8
region   = ap-beijing
  1. main.go

package main

import (
    "fmt"
    "github.com/gin-contrib/sessions"
    _ "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-contrib/sessions/redis"
    "github.com/gin-gonic/gin"
    "gopkg.in/ini.v1"
    "goshop/models"
    "goshop/routers"
    "html/template"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    //初始化路由,会设置默认中间件:engine.Use(Logger(), Recovery()),可以使用gin.New()来设置路由
    r := gin.Default()

    //初始化基于redis的存储引擎: 需要启动redis服务,不然会报错
    //参数说明:
    //自第1个参数-redis最大的空闲连接数
    //第2个参数-数通信协议tcp或者udp
    //第3个参数-redis地址,格式,host:port 第4个参数-redis密码
    //第5个参数-session加密密钥
    store,_:=redis.NewStore(10,"tcp","localhost:6379","",[]byte("secret"))
    r.Use(sessions.Sessions("mysession",store))

    //自定义模板函数,必须在r.LoadHTMLGlob前面(只调用,不执行, 可以在html 中使用)
    r.SetFuncMap(template.FuncMap{
        "UnixToTime": models.UnixToTime, //注册模板函数
        "Str2Html": models.Str2Html, // 把字符串解析成html
        "FormatImg": models.FormatImg, //格式化图片:判断是否开启了oss
    })
    //加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行
    //如果模板在多级目录里面的话需要这样配置 r.LoadHTMLGlob("templates/**/**/*") /** 表示目录
    //LoadHTMLGlob只能加载同一层级的文件
    //比如说使用router.LoadHTMLFile("/templates/**/*"),就只能加载/templates/admin/或者/templates/order/下面的文件
    //解决办法就是通过filepath.Walk来搜索/templates下的以.html结尾的文件,把这些html文件都加载一个数组中,然后用LoadHTMLFiles加载
    //r.LoadHTMLGlob("templates/**/**/*")
    var files []string
    filepath.Walk("./templates", func(path string, info os.FileInfo, err error) error {
        if strings.HasSuffix(path, ".html") {
            files = append(files, path)
        }
        return nil
    })
    r.LoadHTMLFiles(files...)

    //配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录
    r.Static("/static", "./static")

    //分组路由文件
    routers.AdminRoutersInit(r)
    routers.ApiRoutersInit(r)
    routers.FrontendRoutersInit(r)

    r.Run() // 启动一个web服务
}

4.创建商品相关模型

在models下创建商品相关模型,汇总如下:
商品分类模型 GoodsCate.go
商品类型模型GoodType.go
商品类型属性模型 GoodsTypeAttribute.go
商品颜色模型 GoodsColor.go
商品模型 Goods.go
商品图片模型GoodsImage.go
商品属性模型GoodsAttr.go
package models

//商品分类

type GoodsCate struct {
    Id             int
    Title          string  // 标题
    CateImg        string  // 分类图片
    Link           string  // 跳转地址
    Template       string  // 加载的模板: 为空的话加载默认模板, 不为空的话加载自定义模板
    Pid            int        // 上级id: 为0的话则是顶级分类
    SubTitle       string    // SEO标题
    Keywords       string    // SEO关键字
    Description    string    // SEO描述
    Sort           int    // 排序
    Status         int    // 状态: 1 显示, 0 隐藏
    AddTime        int    // 添加时间
    GoodsCateItems []GoodsCate `gorm:"foreignKey:pid;references:Id"` // 关联自身,下级分类
}

func (GoodsCate) TableName() string {
    return "goods_cate"
}
package models

//商品类型

type GoodsType struct {
    Id          int
    Title       string  // 类型名称
    Description string  // 介绍
    Status      int  // 状态
    AddTime     int  // 添加时间
}

func (GoodsType) TableName() string {
    return "goods_type"
}
package models

// 商品类型属性设置

type GoodsTypeAttribute struct {
    Id        int `json:"id"`  // HTML页面使用名称
    CateId    int `json:"cate_id"`   //商品类型id:商品类型表goods_type.id
    Title     string `json:"title"`   // 属性名称
    AttrType  int `json:"attr_type"`   //属性录入方式: 1 单行文本框, 2 多行文本框, 3 从下面列表中选择(一行代表一个可选值)
    AttrValue string `json:"attr_value"`   //可选值列表
    Status    int `json:"status"`   // 状态
    Sort      int `json:"sort"`   //排序
    AddTime   int `json:"add_time"`   //增加时间
}

func (GoodsTypeAttribute) TableName() string {
    return "goods_type_attribute"
}
package models

//商品颜色

type GoodsColor struct {
    Id         int
    ColorName  string // 颜色名称
    ColorValue string // 颜色值
    Status     int    // 状态
    Checked      bool   `gorm:"-"` //忽略该字段
}

func (GoodsColor) TableName() string {
    return "goods_color"
}
package models

//商品表

type Goods struct {
    Id            int
    Title         string    //商品标题
    SubTitle      string    //附属标题
    GoodsSn       string    //商品编号
    CateId        int    //商品分类id: goods_cate.id
    ClickCount    int    //商品点击数量
    GoodsNumber   int    //商品库存
    Price         float64    //价格
    MarketPrice   float64    //商品市场价(原价)
    RelationGoods string    //关联商品id,如: 1, 23,55 ,商品id以逗号隔开
    GoodsAttr     string    //商品更多属性
    GoodsVersion  string    //商品版本
    GoodsImg      string    //图片
    GoodsGift     string    //商品赠品
    GoodsFitting  string    //商品配件
    GoodsColor    string    //颜色
    GoodsKeywords string    //SEO关键字
    GoodsDesc     string    //SEO商品描述
    GoodsContent       string    //商品详情
    IsDelete      int    //是否删除
    IsHot         int    //是否热销
    IsBest        int    //是否精品
    IsNew         int    //是否新品
    GoodsTypeId   int    //商品类型id,关联GoodsType.Id
    Sort          int    //排序
    Status        int    //状态
    AddTime       int    //添加时间
}

func (Goods) TableName() string {
    return "goods"
}
package models

//商品图片

type GoodsImage struct {
    Id      int
    GoodsId int        //商品id
    ImgUrl  string    //图片保存路径:一般只会保存类似于/static/upload/20230313/xxx.png这种格式
    ColorId int        //颜色id
    Sort    int
    AddTime int
    Status  int
}

func (GoodsImage) TableName() string {
    return "goods_image"
}
package models

//商品属性

type GoodsAttr struct {
    Id              int
    GoodsId         int        //商品id
    AttributeCateId int        //商品类型id,关联GoodsType.Id
    AttributeId     int        //商品类型属性id,关联GoodsTypeAttribute.Id
    AttributeTitle  string    //类型属性标题
    AttributeType   int        //类型属性录入方式:GoodsTypeAttribute.AttrType
    AttributeValue  string  //类型属性值
    Sort            int
    AddTime         int
    Status          int
}

func (GoodsAttr) TableName() string {
    return "goods_attr"
}

5.创建商品控制器

在controllers下创建GoodsController.go控制器,该控制器中的方法功能详解:
(1).Index方法:
商品列表展示,分页查询,搜索条件判断查询,
(2).Add方法:
进入增加商品页面,提供商品分类,商品颜色,商品类型数据,以供选择
(3).DoAdd方法:
商品信息表单提交功能,获取表单提交过来的数据,并进行判断是否合法,商品颜色信息处理(把颜色转换成字符串),上传图片,生成缩略图,增加图库信息,增加规格包装
(4).Edit方法
编辑商品,获取要修改的商品信息,以及商品分类数据,商品颜色的转换(把商品颜色字符串转换成切片数组),商品颜色列表,商品图库信息,商品类型,规格信息(拼接规格信息表单html),以供商品编辑页面使用
(5).DoEdit方法
修改商品信息表单提交,获取表单提交过来的数据,并进行判断是否合法,上传图片,生成缩略图,增加图库信息,修改规格包装
(6).GoodsTypeAttribute方法
获取商品类型对应的属性,用于改变商品类型时,ajax获取对应类型的属性数据
(7).EditorImageUpload方法
富文本编辑器上传图片方法,当在富文本上传图片时,使用该方法
(8).GoodsImageUpload方法
商品上传图片方法,当上传商品logo,图集时,使用该方法
(9).ChangeGoodsImageColor方法
修改商品图库关联的颜色,当修改商品图库图片时,可以设置图片颜色
(10).RemoveGoodsImage方法
删除图库,在修改图片时,可以删除图片(数据库保存的图片信息,以及服务器上的图片)
(11).Delete方法
删除商品
package admin

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "goshop/models"
    "math"
    "net/http"
    "os"
    "strings"
    "sync"
)

var wg sync.WaitGroup //可以实现主线程等待协程执行完毕

type GoodsController struct {
    BaseController
}

func (con GoodsController) Index(c *gin.Context) {
    //分页查询
    page, _ := models.Int(c.Query("page")) // 获取分页,当分页page数据格式不正确时, page == 0
    if page == 0 {
        page = 1
    }
    //每页查询的数量
    pageSize := 2

    //定义商品结构体
    goodsList := []models.Goods{}
    //条件
    where := "is_delete=0"
    //关键字查询
    keyword := c.Query("keyword")
    if len(keyword) > 0 {
        where += " and title like \"%" + keyword + "%\""
        //也可以使用下面的方式
        //where += ` and title like "%` + keyword +`%"`
    }
    //分页查询
    models.DB.Where(where).Offset((page - 1) * pageSize).Limit(pageSize).Find(&goodsList)

    //获取总数量
    var count int64
    models.DB.Table("goods").Where(where).Count(&count)
    //计算总页数:math.Ceil()向上取整,注意float64类型
    totalPages := math.Ceil(float64(count) / float64(pageSize))

    //判断最后一页有没有数据,如果没有则跳转到第一页
    if len(goodsList) > 0 {
        c.HTML(http.StatusOK, "admin/goods/index.html", gin.H{
            "goodsList":  goodsList,
            "totalPages": totalPages,
            "page":       page,
            "keyword":    keyword,
        })
        return
    }

    //最后一页没有数据,判断页码数是否等于1,如果不等于,则重定向到列表页
    if page != 1 {
        c.Redirect(http.StatusFound, "/admin/goods")
        return
    }

    c.HTML(http.StatusOK, "admin/goods/index.html", gin.H{
        "goodsList":  goodsList,
        "totalPages": totalPages,
        "page":       page,
        "keyword":    keyword,
    })
}

func (con GoodsController) Add(c *gin.Context) {
    //获取商品分类
    goodsCateList := []models.GoodsCate{}
    models.DB.Where("pid = 0").Preload("GoodsCateItems").Find(&goodsCateList)
    //获取商品颜色
    goodsColorList := []models.GoodsColor{}
    models.DB.Where("status = 1").Find(&goodsColorList)
    //获取商品类型
    goodsTypeList := []models.GoodsType{}
    models.DB.Where("status = 1").Find(&goodsTypeList)

    c.HTML(http.StatusOK, "admin/goods/add.html", gin.H{
        "goodsCateList":  goodsCateList,
        "goodsColorList": goodsColorList,
        "goodsTypeList":  goodsTypeList,
    })
}

//增加商品信息表单提交
func (con GoodsController) DoAdd(c *gin.Context) {
    //获取表单提交过来的数据,并进行判断是否合法
    title := c.PostForm("title")
    subTitle := c.PostForm("sub_title")
    goodsSn := c.PostForm("goods_sn")
    cateId, _ := models.Int(c.PostForm("cate_id"))
    goodsNumber, _ := models.Int(c.PostForm("goods_number"))
    //价格,注意小数点
    marketPrice, _ := models.Float(c.PostForm("market_price"))
    price, _ := models.Float(c.PostForm("price"))

    relationGoods := c.PostForm("relation_goods")
    goodsAttr := c.PostForm("goods_attr")
    goodsVersion := c.PostForm("goods_version")
    goodsGift := c.PostForm("goods_gift")
    goodsFitting := c.PostForm("goods_fitting")
    //颜色:获取的是切片
    goodsColorArr := c.PostFormArray("goods_color")

    goodsKeywords := c.PostForm("goods_keywords")
    goodsDesc := c.PostForm("goods_desc")
    goodsContent := c.PostForm("goods_content")
    isDelete, _ := models.Int(c.PostForm("is_delete"))
    isHot, _ := models.Int(c.PostForm("is_hot"))
    isBest, _ := models.Int(c.PostForm("is_best"))
    isNew, _ := models.Int(c.PostForm("is_new"))
    goodsTypeId, _ := models.Int(c.PostForm("goods_type_id"))
    sort, _ := models.Int(c.PostForm("sort"))
    status, _ := models.Int(c.PostForm("status"))
    addTime := int(models.GetUnix())

    //获取颜色信息,把颜色转换成字符串
    goodsColorStr := strings.Join(goodsColorArr, ",")
    //上传图片,生成缩略图(调用tools.go 工具中的方法)
    goodsImg, _ := models.UploadImg(c, "goods_img")
    if len(goodsImg) > 0 {
        //判断本地图片才需要处理缩略图(调用tools.go 工具中的方法)
        if models.GetOssStatus() != 1 {
            //开启协程
            wg.Add(1)
            go func() {
                models.ResizeGoodsImage(goodsImg)
                wg.Done()
            }()
        }
    }
    //增加商品数据
    //实例化商品结构体
    goods := models.Goods{
        Title:         title,
        SubTitle:      subTitle,
        GoodsSn:       goodsSn,
        CateId:        cateId,
        ClickCount:    100,
        GoodsNumber:   goodsNumber,
        MarketPrice:   marketPrice,
        Price:         price,
        RelationGoods: relationGoods,
        GoodsAttr:     goodsAttr,
        GoodsVersion:  goodsVersion,
        GoodsGift:     goodsGift,
        GoodsFitting:  goodsFitting,
        GoodsKeywords: goodsKeywords,
        GoodsDesc:     goodsDesc,
        GoodsContent:  goodsContent,
        IsDelete:      isDelete,
        IsHot:         isHot,
        IsBest:        isBest,
        IsNew:         isNew,
        GoodsTypeId:   goodsTypeId,
        Sort:          sort,
        Status:        status,
        AddTime:       addTime,
        GoodsColor:    goodsColorStr,
        GoodsImg:      goodsImg,
    }
    err := models.DB.Create(&goods).Error
    if err != nil {
        con.Error(c, "增加失败", "/admin/goods/add")
        return
    }
    //增加图库信息
    //开启协程
    wg.Add(1)
    go func() {
        goodsImageList := c.PostFormArray("goods_image_list") //获取图片切片
        for _, v := range goodsImageList {
            goodsImgObj := models.GoodsImage{}
            goodsImgObj.GoodsId = goods.Id
            goodsImgObj.ImgUrl = v
            goodsImgObj.Sort = 10
            goodsImgObj.Status = 1
            goodsImgObj.AddTime = int(models.GetUnix())
            models.DB.Create(&goodsImgObj)
        }
        wg.Done()
    }()

    //增加规格包装
    wg.Add(1) //启动一个 goroutine 就登记+1
    //商品类型属性id和商品类型属性值一一对应
    go func() {
        attrIdList := c.PostFormArray("attr_id_list")
        attrValueList := c.PostFormArray("attr_value_list")
        for i := 0; i < len(attrIdList); i++ {
            //获取商品类型属性id
            goodsTypeAttributeId, attributeIdErr := models.Int(attrIdList[i])
            if attributeIdErr == nil {
                //获取商品类型属性的数据
                goodsTypeAttributeObj := models.GoodsTypeAttribute{Id: goodsTypeAttributeId}
                models.DB.Find(&goodsTypeAttributeObj)
                //给商品属性里面增加数据  规格包装
                goodsAttrObj := models.GoodsAttr{}
                goodsAttrObj.GoodsId = goods.Id
                goodsAttrObj.AttributeTitle = goodsTypeAttributeObj.Title
                goodsAttrObj.AttributeType = goodsTypeAttributeObj.AttrType
                goodsAttrObj.AttributeId = goodsTypeAttributeObj.Id
                goodsAttrObj.AttributeCateId = goodsTypeAttributeObj.CateId
                goodsAttrObj.AttributeValue = attrValueList[i] //值从attrValueList中获取
                goodsAttrObj.Status = 1
                goodsAttrObj.Sort = 10
                goodsAttrObj.AddTime = int(models.GetUnix())
                models.DB.Create(&goodsAttrObj)
            }
        }
        wg.Done() //goroutine 结束就登记-1
    }()
    //等待所有登记的 goroutine 都结束
    wg.Wait()

    con.Success(c, "增加数据成功", "/admin/goods")
}

func (con GoodsController) Edit(c *gin.Context) {
    //获取要修改的商品信息
    id, err := models.Int(c.Query("id"))
    if err != nil {
        con.Error(c, "传入参数错误", "/admin/goods")
        return
    }
    goods := models.Goods{Id: id}
    models.DB.Find(&goods)

    //获取商品分类
    goodsCateList := []models.GoodsCate{}
    models.DB.Where("pid = 0").Preload("GoodsCateItems").Find(&goodsCateList)

    //获取商品颜色,以及选择的颜色
    //把商品颜色字符串转换成切片数组
    goodsColorSlice := strings.Split(goods.GoodsColor, ",")
    //定义一个商品颜色Map
    goodsColorMap := make(map[string]string)
    //循环颜色切片,把数据放入Map中
    for _, v := range goodsColorSlice {
        goodsColorMap[v] = v
    }
    //获取商品颜色列表
    goodsColorList := []models.GoodsColor{}
    models.DB.Where("status = 1").Find(&goodsColorList)
    //循环颜色列表,并与goodsColorMap比较,判断该商品是否有该颜色,并设置check
    for i := 0; i < len(goodsCateList); i++ {
        //断该商品是否有该颜色
        _, ok := goodsColorMap[models.String(goodsColorList[i].Id)]
        if ok { //该商品存在该颜色,设置Check=true
            goodsColorList[i].Checked = true
        }
    }

    //获取商品图库信息
    goodsImageList := []models.GoodsImage{}
    models.DB.Where("goods_id = ?", goods.Id).Find(&goodsImageList)

    //获取商品类型
    goodsTypeList := []models.GoodsType{}
    models.DB.Where("status = 1").Find(&goodsTypeList)

    //获取规格信息
    goodsAttr := []models.GoodsAttr{}
    models.DB.Where("goods_id = ?", goods.Id).Find(&goodsAttr)
    //拼接规格信息表单html
    goodsAttrStr := ""
    //循环规格信息数
    for _, v := range goodsAttr {
        if v.AttributeType == 1 { //当属性类型=1(单行文本框)时,显示的input html
            //fmt.Sprintf(): 拼接字符串
            goodsAttrStr += fmt.Sprintf(`
  • %v:
  • `, v.AttributeTitle, v.AttributeId, v.AttributeValue) } else if v.AttributeType == 2 { //当属性类型=2(多行文本框)时,显示的textareahtml goodsAttrStr += fmt.Sprintf(`
  • %v:  
  • `, v.AttributeTitle, v.AttributeId, v.AttributeValue) } else if v.AttributeType == 3 { //当属性类型=3(下拉框选择)时,显示的select html //获取当前类型对应的值(下拉框应该有多个选择的值) goodsTypeAttribute := models.GoodsTypeAttribute{Id: v.AttributeId} models.DB.Find(&goodsTypeAttribute) //把下拉框中的值转换成切片 attrValueSlice := strings.Split(goodsTypeAttribute.AttrValue, "\n") //属性id input hidden goodsAttrStr += fmt.Sprintf(`
  • %v:   `, v.AttributeTitle, v.AttributeId) goodsAttrStr += fmt.Sprintf(``) goodsAttrStr += fmt.Sprintf(`
  • `) } } c.HTML(http.StatusOK, "admin/goods/edit.html", gin.H{ "goods": goods, "goodsCateList": goodsCateList, "goodsColorList": goodsColorList, "goodsTypeList": goodsTypeList, "goodsAttrStr": goodsAttrStr, "goodsImageList": goodsImageList, "prePage": c.Request.Referer(), // 获取上一页的地址 }) } //修改商品信息表单提交 func (con GoodsController) DoEdit(c *gin.Context) { //获取表单提交过来的数据,并进行判断是否合法 id, err1 := models.Int(c.PostForm("id")) if err1 != nil { con.Error(c, "传入参数错误", "/admin/goods") return } prePage := c.PostForm("prePage") // 获取上一页地址 title := c.PostForm("title") subTitle := c.PostForm("sub_title") goodsSn := c.PostForm("goods_sn") cateId, _ := models.Int(c.PostForm("cate_id")) goodsNumber, _ := models.Int(c.PostForm("goods_number")) //价格,注意小数点 marketPrice, _ := models.Float(c.PostForm("market_price")) price, _ := models.Float(c.PostForm("price")) relationGoods := c.PostForm("relation_goods") goodsAttr := c.PostForm("goods_attr") goodsVersion := c.PostForm("goods_version") goodsGift := c.PostForm("goods_gift") goodsFitting := c.PostForm("goods_fitting") //颜色:获取的是切片 goodsColorArr := c.PostFormArray("goods_color") goodsKeywords := c.PostForm("goods_keywords") goodsDesc := c.PostForm("goods_desc") goodsContent := c.PostForm("goods_content") isDelete, _ := models.Int(c.PostForm("is_delete")) isHot, _ := models.Int(c.PostForm("is_hot")) isBest, _ := models.Int(c.PostForm("is_best")) isNew, _ := models.Int(c.PostForm("is_new")) goodsTypeId, _ := models.Int(c.PostForm("goods_type_id")) sort, _ := models.Int(c.PostForm("sort")) status, _ := models.Int(c.PostForm("status")) //获取颜色信息,把颜色转换成字符串 goodsColorStr := strings.Join(goodsColorArr, ",") //修改数据 goods := models.Goods{Id: id} models.DB.Find(&goods) goods.Title = title goods.SubTitle = subTitle goods.GoodsSn = goodsSn goods.CateId = cateId goods.GoodsNumber = goodsNumber goods.MarketPrice = marketPrice goods.Price = price goods.RelationGoods = relationGoods goods.GoodsAttr = goodsAttr goods.GoodsVersion = goodsVersion goods.GoodsGift = goodsGift goods.GoodsFitting = goodsFitting goods.GoodsKeywords = goodsKeywords goods.GoodsDesc = goodsDesc goods.GoodsContent = goodsContent goods.IsDelete = isDelete goods.IsHot = isHot goods.IsBest = isBest goods.IsNew = isNew goods.GoodsTypeId = goodsTypeId goods.Sort = sort goods.Status = status goods.GoodsColor = goodsColorStr //上传图片,生成缩略图 goodsImg, err2 := models.UploadImg(c, "goods_img") if err2 == nil && len(goodsImg) > 0 { // 说明修改了图片,那么就要设置图片属性 goods.GoodsImg = goodsImg //判断本地图片才需要处理缩略图 if models.GetOssStatus() != 1 { //开启协程 wg.Add(1) go func() { models.ResizeGoodsImage(goodsImg) wg.Done() }() } } err := models.DB.Save(&goods).Error if err != nil { con.Error(c, "修改失败", "/admin/goods/edit?id="+models.String(id)) return } //增加图库信息 //开启协程 wg.Add(1) go func() { goodsImageList := c.PostFormArray("goods_image_list") //获取图片切片 for _, v := range goodsImageList { goodsImgObj := models.GoodsImage{} goodsImgObj.GoodsId = goods.Id goodsImgObj.ImgUrl = v goodsImgObj.Sort = 10 goodsImgObj.Status = 1 goodsImgObj.AddTime = int(models.GetUnix()) models.DB.Create(&goodsImgObj) } wg.Done() }() //修改规格包装:1.删除当前商品下面的规格包装,2.重新执行增加 //1.删除当前商品下面的规格包装 goodsAttrObj := models.GoodsAttr{} models.DB.Where("goods_id = ?", goods.Id).Delete(&goodsAttrObj) //2.重新执行增加 wg.Add(1) //启动一个 goroutine 就登记+1 //商品类型属性id和商品类型属性值一一对应 go func() { attrIdList := c.PostFormArray("attr_id_list") attrValueList := c.PostFormArray("attr_value_list") for i := 0; i < len(attrIdList); i++ { //获取商品类型属性id goodsTypeAttributeId, attributeIdErr := models.Int(attrIdList[i]) if attributeIdErr == nil { //获取商品类型属性的数据 goodsTypeAttributeObj := models.GoodsTypeAttribute{Id: goodsTypeAttributeId} models.DB.Find(&goodsTypeAttributeObj) //给商品属性里面增加数据 规格包装 goodsAttrObj := models.GoodsAttr{} goodsAttrObj.GoodsId = goods.Id goodsAttrObj.AttributeTitle = goodsTypeAttributeObj.Title goodsAttrObj.AttributeType = goodsTypeAttributeObj.AttrType goodsAttrObj.AttributeId = goodsTypeAttributeObj.Id goodsAttrObj.AttributeCateId = goodsTypeAttributeObj.CateId goodsAttrObj.AttributeValue = attrValueList[i] //值从attrValueList中获取 goodsAttrObj.Status = 1 goodsAttrObj.Sort = 10 goodsAttrObj.AddTime = int(models.GetUnix()) models.DB.Create(&goodsAttrObj) } } wg.Done() //goroutine 结束就登记-1 }() //等待所有登记的 goroutine 都结束 wg.Wait() if len(prePage) > 0 { //跳转到上一页 con.Success(c, "修改数据成功", prePage) return } con.Success(c, "修改数据成功", "/admin/goods") } //获取商品类型对应的属性 func (con GoodsController) GoodsTypeAttribute(c *gin.Context) { cateId, err1 := models.Int(c.Query("cateId")) goodsTypeAttributeList := []models.GoodsTypeAttribute{} err2 := models.DB.Where("cate_id = ?", cateId).Find(&goodsTypeAttributeList).Error if err1 != nil || err2 != nil { c.JSON(http.StatusOK, gin.H{ "success": false, "result": "", }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "result": goodsTypeAttributeList, }) } //富文本编辑器上传图片方法 func (con GoodsController) EditorImageUpload(c *gin.Context) { imgDir, err := models.UploadImg(c, "file") //传递的参数默认是file if err != nil { c.JSON(http.StatusOK, gin.H{ "link": "", //富文本要求返回的格式: {link: 'path/to/image.jpg'} }) return } //判断本地图片才需要处理缩略图 if models.GetOssStatus() != 1 { //开启协程 wg.Add(1) go func() { models.ResizeGoodsImage(imgDir) wg.Done() }() //本地图片,返回本地图片地址 c.JSON(http.StatusOK, gin.H{ "link": "/" + imgDir, }) } else { //云服务器对象存储图片,返回云服务器图片地址 c.JSON(http.StatusOK, gin.H{ "link": models.GetSettingFromColumn("OssDomain") + imgDir, }) } } //商品上传图片方法 func (con GoodsController) GoodsImageUpload(c *gin.Context) { imgDir, err := models.UploadImg(c, "file") //传递的参数默认是file if err != nil { c.JSON(http.StatusOK, gin.H{ "link": "", //富文本要求返回的格式: {link: 'path/to/image.jpg'} }) return } //判断本地图片才需要处理缩略图 if models.GetOssStatus() != 1 { //开启协程 wg.Add(1) go func() { models.ResizeGoodsImage(imgDir) wg.Done() }() } //返回图片地址 c.JSON(http.StatusOK, gin.H{ "link": imgDir, }) } //修改商品图库关联的颜色 func (con GoodsController) ChangeGoodsImageColor(c *gin.Context) { //获取图片id 获取颜色id goodsImageId, err1 := models.Int(c.Query("goods_image_id")) colorId, err2 := models.Int(c.Query("color_id")) goodsImage := models.GoodsImage{Id: goodsImageId} models.DB.Find(&goodsImage) goodsImage.ColorId = colorId err3 := models.DB.Save(&goodsImage).Error if err1 != nil || err2 != nil || err3 != nil { c.JSON(http.StatusOK, gin.H{ "result": "更新失败", "success": false, }) return } c.JSON(http.StatusOK, gin.H{ "result": "更新成功", "success": true, }) } //删除图库 func (con GoodsController) RemoveGoodsImage(c *gin.Context) { //获取图片id goodsImageId, err1 := models.Int(c.Query("goods_image_id")) goodsImage := models.GoodsImage{Id: goodsImageId} //获取图片 models.DB.Find(&goodsImage) fileName := goodsImage.ImgUrl //todo 是否删除服务器保存的图片? err3 := os.Remove(strings.TrimLeft(fileName, "/")) if err3 != nil { c.JSON(http.StatusOK, gin.H{ "result": err3.Error(), "success": false, }) return } //删除数据库中的数据 err2 := models.DB.Delete(&goodsImage).Error if err1 != nil || err2 != nil { c.JSON(http.StatusOK, gin.H{ "result": "删除失败", "success": false, }) return } c.JSON(http.StatusOK, gin.H{ "result": "删除成功", "success": true, }) } //删除 func (con GoodsController) Delete(c *gin.Context) { //获取提交的表单数据 id, err := models.Int(c.Query("id")) if err != nil { con.Error(c, "传入数据错误", "/admin/goods") return } //查询商品 goods := models.Goods{Id: id} models.DB.Find(&goods) //软删除 goods.IsDelete = 1 goods.Status = 0 models.DB.Save(&goods) //获取上一页地址,判断是否存在,如果存在则跳转,不存在则跳转到列表首页 prePage := c.Request.Referer() if len(prePage) > 0 { con.Success(c, "删除数据成功", prePage) return } con.Success(c, "删除数据成功", "/admin/goods") }

    6.创建商品相关html

    index.html

    该商品列表有如下功能
    1.增加商品按钮:跳转到增加商品页面
    2.搜索功能:输入商品名称,点击搜索
    3.修改商品字段(上架,精品,新平,热销)状态
    4.修改排序数字
    5.修改操作:点击修改跳转到修改页面
    6.删除操作
    7.分页操作
    效果展示见 :二.商品相关界面展示
    这里要用到一个 分页组件jqPaginator,下载链接:
    链接: https://pan.baidu.com/s/1hype6KGLcYK2GBfiJZOCBA
    提取码:5pze
    {{ define "admin/goods/index.html" }}
    {{ template "admin/public/page_header.html" .}}
    
    
        
    {{range $key,$value := .goodsList}} {{end}}
    商品名称 价格 原价 点击量 上架 精品 新品 热销 排序 库存 操作
    {{$value.Title}} {{$value.Price}} {{$value.MarketPrice}} {{$value.ClickCount}} {{if eq $value.Status 1}} {{else}} {{end}} {{if eq $value.IsBest 1}} {{else}} {{end}} {{if eq $value.IsNew 1}} {{else}} {{end}} {{if eq $value.IsHot 1}} {{else}} {{end}} {{$value.Sort}} {{$value.GoodsNumber}} 修改   删除
    {{end}}

    add.html

    效果展示与功能介绍见 :二.商品相关界面展示
    这里需要用到一个富文本编辑器(wysiwyg-editor),以及对应的语言包(zh_cn.js),下载链接: https://pan.baidu.com/s/1HgUUhMBemHmH-gXchBBOXg ,提取码:1b3n,批量上传图片插(diyUpload)),下载链接: https://pan.baidu.com/s/1Q0If0tygsKU3d6PM4hs1GA ,提取码:xq3m
    {{ define "admin/goods/add.html" }}
        {{ template "admin/public/page_header.html" .}}
        
        
        
        
        
        
    
        
        
        
        
        
        
    
        
    增加商品
    • 商品标题:
    • 附属标题:
    • 商品版本:
    • 所属分类:
    • 商品图片:
    • 商品价格:
    • 商品原价:
    • 商品状态:  显示   隐藏
    • 加入推荐:  精品 热销 新品
    • 商品颜色: {{range $key, $value := .goodsColorList}} {{end}}
    • 关联商品: 填写关联商品的id 多个以逗号隔开 格式:23,24,39
    • 关联赠品: 可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量
    • 关联配件: 可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量
    • 更多属性: 格式: 颜色:红色,白色,黄色 | 尺寸:41,42,43
    • SEO关键词:
    • SEO描述:
    • 商品类型: 

    {{end}}

    edit.html

    {{ define "admin/goods/edit.html" }}
    {{ template "admin/public/page_header.html" .}}
    
    
    
    
    
    
    
    
    
    
    
    
    修改商品
    • 商品标题:
    • 附属标题:
    • 商品版本:
    • 所属分类:
    • 商品图片: {{if ne .goods.GoodsImg ""}} {{end}}
    • 商品价格:
    • 商品原价:
    • 商品库存:
    • 商品排序:
    • 商品状态:  显示   隐藏
    • 加入推荐:  精品 热销 新品
    • 商品颜色: {{range $key,$value := .goodsColorList}}   {{end}}
    • 关联商品: 填写关联商品的id 多个以逗号隔开 格式:23,24,39
    • 关联赠品: 可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量
    • 关联配件: 可为空 格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量
    • 更多属性: 格式: 颜色:红色,白色,黄色 | 尺寸:41,42,43
    • Seo关键词:
    • Seo描述:
    • 商品类型: 
      {{.goodsAttrStr | Str2Html}}
      {{$goodsColor:=.goodsColorList}} {{range $key,$value := .goodsImageList}}
    • {{end}}

    {{end}}

    7.配置商品相关路由

    在routers/adminRouters.go下配置商品相关路由
    //商品路由
    adminRouters.GET("/goods", admin.GoodsController{}.Index)
    adminRouters.GET("/goods/add", admin.GoodsController{}.Add)
    adminRouters.POST("/goods/doAdd", admin.GoodsController{}.DoAdd)
    adminRouters.GET("/goods/edit", admin.GoodsController{}.Edit)
    adminRouters.POST("/goods/doEdit", admin.GoodsController{}.DoEdit)
    adminRouters.GET("/goods/delete", admin.GoodsController{}.Delete)
    
    //上传商品图片(商品头图,商品图片相册)
    adminRouters.POST("/goods/goodsImageUpload", admin.GoodsController{}.GoodsImageUpload)
    //商品富文本编辑器图片上传
    adminRouters.POST("/goods/editorImageUpload", admin.GoodsController{}.EditorImageUpload)
    //获取商品类型对应的属性
    adminRouters.GET("/goods/goodsTypeAttribute", admin.GoodsController{}.GoodsTypeAttribute)
    //改变图库中相关图片颜色
    adminRouters.GET("/goods/changeGoodsImageColor", admin.GoodsController{}.ChangeGoodsImageColor)
    //删除图片
    adminRouters.GET("/goods/removeGoodsImage", admin.GoodsController{}.RemoveGoodsImage)

    [上一节][golang gin框架] 19.Gin 图片上传到云服务器(腾讯云,阿里云)

    [下一节][golang gin框架] 21.Gin 商城项目-导航模块功能

    你可能感兴趣的:(#,gin框架开发,golang,golang,gin,jqPaginator,wysiwyg-editor,diyUpload)