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

news2024/11/25 16:45:06

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

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

二.商品相关界面展示

商品列表

该商品列表有如下功能
1.增加商品按钮:跳转到增加商品页面
2.搜索功能:输入商品名称,点击搜索
3.修改商品字段(上架,精品,新平,热销)状态
4.修改排序数字
5.修改操作:点击修改跳转到修改页面
6.删除操作
7.分页操作

添加商品

通用信息

功能如下:
1.输入商品相关信息
2.选择商品所属分类(从商品分类表goods_cate中选择)
3.上传商品logo图片
4.选择商品状态(单选)
5.选择商品(新品,热销,精品:复选框)

详细描述

功能:
1.引入富文本框,输入商品内容
2.上传商品相关图片到富文本
注意:这里图片上传后台要判断是否上传到云服务器

商品属性

功能:
1.选择商品颜色(多选):从商品颜色表good_color中选择
2.输入商品其他相关属性

规格与包装

功能:
1.选择商品类型:从商品类型表goods_type中选择
2.根据选择的商品类型,展示不同的商品类型属性:从商品类型属性表goods_type_attribute中选择
3.填写商品类型属性

商品相册

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

修改商品

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

删除商品

修改属性状态,排序等

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

三.代码展示

商品相关数据表见 [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(`<li><span>%v: </span> <input type="hidden" name="attr_id_list" value="%v" />   <input type="text" name="attr_value_list" value="%v" /></li>`, v.AttributeTitle, v.AttributeId, v.AttributeValue)
        } else if v.AttributeType == 2 { //当属性类型=2(多行文本框)时,显示的textareahtml
            goodsAttrStr += fmt.Sprintf(`<li><span>%v:  </span><input type="hidden" name="attr_id_list" value="%v" />  <textarea cols="50" rows="3" name="attr_value_list">%v</textarea></li>`, 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(`<li><span>%v:  </span>  <input type="hidden" name="attr_id_list" value="%v" /> `, v.AttributeTitle, v.AttributeId)
            goodsAttrStr += fmt.Sprintf(`<select name="attr_value_list">`)
            //循环切片, 生成下拉框option
            for i := 0; i < len(attrValueSlice); i++ {
                if attrValueSlice[i] == v.AttributeValue { // 当前商品下拉属性 == 对应的商品下拉属性时, selected
                    goodsAttrStr += fmt.Sprintf(`<option value="%v" selected >%v</option>`, attrValueSlice[i], attrValueSlice[i])
                } else {
                    goodsAttrStr += fmt.Sprintf(`<option value="%v">%v</option>`, attrValueSlice[i], attrValueSlice[i])
                }
            }
            goodsAttrStr += fmt.Sprintf(`</select>`)
            goodsAttrStr += fmt.Sprintf(`</li>`)
        }
    }

    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" .}}
<script type="text/javascript" src="/static/admin/js/jqPaginator.js"></script>

    <div class="container-fluid">
        <div class="row">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <a href="/admin/goods/add" class="btn btn-primary">增加商品</a>
                </div>
                <div class="panel-body">
                    <form role="form" class="form-inline" method="get" action="/admin/goods">
                        <div class="form-group">
                            <label for="name">输入关键词</label>
                            <input type="text" class="form-control" value="{{.keyword}}" id="keyword" name="keyword" placeholder="请输入名称">
                        </div>

                        <div class="form-group">
                            <button type="submit" class="btn btn-default">开始搜索</button>
                        </div>
                    </form>
                </div>
                <!--
                    列表展示
                -->
                <div class="table-responsive">
                    <table class="table table-bordered">
                        <thead>
                        <tr class="th">
                            <th>商品名称</th>
                            <th>价格</th>
                            <th>原价</th>
                            <th>点击量</th>
                            <th>上架</th>
                            <th>精品</th>
                            <th>新品</th>
                            <th>热销</th>
                            <th>排序</th>
                            <th>库存</th>
                            <th class="text-center">操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {{range $key,$value := .goodsList}}

                            <tr>
                                <td>{{$value.Title}}</td>
                                <td>{{$value.Price}}</td>
                                <td>{{$value.MarketPrice}}</td>
                                <td>{{$value.ClickCount}}</td>

                                <td class="text-center">
                                    {{if eq $value.Status 1}}
                                        <img class="chStatus" src="/static/admin/images/yes.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="status"/>
                                    {{else}}
                                        <img class="chStatus" src="/static/admin/images/no.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="status"/>
                                    {{end}}
                                </td>

                                <td class="text-center">
                                    {{if eq $value.IsBest 1}}
                                        <img class="chStatus" src="/static/admin/images/yes.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_best"/>
                                    {{else}}
                                        <img class="chStatus" src="/static/admin/images/no.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_best"/>
                                    {{end}}
                                </td>

                                <td class="text-center">
                                    {{if eq $value.IsNew 1}}
                                        <img class="chStatus" src="/static/admin/images/yes.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_new"/>
                                    {{else}}
                                        <img class="chStatus" src="/static/admin/images/no.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_new"/>
                                    {{end}}
                                </td>

                                <td class="text-center">
                                    {{if eq $value.IsHot 1}}
                                        <img class="chStatus" src="/static/admin/images/yes.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_hot"/>
                                    {{else}}
                                        <img class="chStatus" src="/static/admin/images/no.gif" data-id="{{$value.Id}}"
                                             data-table="goods" data-field="is_hot"/>
                                    {{end}}
                                </td>

                                <td class="text-center">
                                    <span class="chSpanNum" data-id="{{$value.Id}}" data-table="goods"
                                          data-field="sort">{{$value.Sort}}</span>
                                </td>

                                <td class="text-center">
                                    <span class="chSpanNum" data-id="{{$value.Id}}" data-table="goods"
                                          data-field="goods_number">{{$value.GoodsNumber}}</span>
                                </td>

                                <td class="text-center">
                                    <a href="/admin/goods/edit?id={{$value.Id}}"/>修改</a>  
                                    <a class="delete" href="/admin/goods/delete?id={{$value.Id}}"/>删除</a>
                                </td>
                            </tr>
                        {{end}}
                        </tbody>
                    </table>
                </div>
            </div>
            <div class="pagination" id="pagination"></div>
        </div>
    </div>

    </body>
<script>
    $('#pagination').jqPaginator({
        totalPages: {{.totalPages}},
        visiblePages: 10,
        currentPage:  {{.page}},
        onPageChange: function (num, type) {
            if (type != "init") {
                location.href = "/admin/goods?page=" + num + "&keyword=" + {{.keyword}};
            }
        }
    });
</script>
</html>
{{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" .}}
    <!-- 富文本编辑器start. -->
    <link href="/static/wysiwyg-editor/css/froala_editor.pkgd.min.css"
          rel="stylesheet" type="text/css"/>
    <script type="text/javascript"
            src="/static/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script>
    <!--语言包-->
    <script type="text/javascript" src="/static/admin/js/zh_cn.js"></script>
    <!-- 富文本编辑器end. -->

    <!-- 批量上传图片插件start. -->
    <link rel="stylesheet" type="text/css" href="/static/diyUpload/css/webuploader.css">
    <link rel="stylesheet" type="text/css" href="/static/diyUpload/css/diyUpload.css">
    <script type="text/javascript" src="/static/diyUpload/js/webuploader.html5only.min.js"></script>
    <script type="text/javascript" src="/static/diyUpload/js/diyUpload.js"></script>
    <!-- 批量上传图片插件end. -->

    <div class="container-fluid">
        <div class="row">
            <div class="panel panel-default">
                <div class="panel-heading">
                    增加商品
                </div>
                <div class="panel-body">
                    <div class="table-responsive goods-content input-form">
                        <form action="/admin/goods/doAdd" method="post" enctype="multipart/form-data">
                            <!-- Nav tabs -->
                            <ul class="nav nav-tabs" role="tablist">
                                <li role="presentation" class="active"><a href="#general" role="tab" data-toggle="tab">通用信息</a>
                                </li>
                                <li role="presentation"><a href="#detail" role="tab" data-toggle="tab">详细描述</a></li>
                                <li role="presentation"><a href="#mix" role="tab" data-toggle="tab">商品属性</a></li>
                                <li role="presentation"><a href="#attribute" role="tab" data-toggle="tab">规格与包装</a></li>
                                <li role="presentation"><a href="#photo" role="tab" data-toggle="tab">商品相册</a></li>
                            </ul>

                            <!-- Tab panes -->
                            <div class="tab-content">
                                <div role="tabpanel" class="tab-pane active" id="general">
                                    <ul class="form_input">
                                        <li><span> 商品标题:</span> <input type="text" name="title" class="input"/></li>
                                        <li><span> 附属标题:</span> <input type="text" name="sub_title" class="input"/></li>
                                        <li><span>商品版本:</span> <input type="text" name="goods_version" class="input"/>
                                        </li>
                                        <li><span>所属分类:</span>

                                            <select name="cate_id" id="cate_id">
                                                {{range $key, $value := .goodsCateList}}
                                                    <option value="{{$value.Id}}">{{$value.Title}}</option>
                                                    {{range $k,$v := $value.GoodsCateItems}}
                                                        <option value="{{$v.Id}}"> ----{{$v.Title}}</option>
                                                    {{end}}
                                                {{end}}
                                            </select>
                                        </li>
                                        <li><span> 商品图片:</span> <input type="file" name="goods_img"/></li>
                                        <li><span>商品价格:</span> <input type="text" name="price"/></li>
                                        <li><span>商品原价:</span> <input type="text" name="market_price"/></li>

                                        <li><span>商品状态:</span> 
                                            <input type="radio" value="1" name="status" checked/> 显示  
                                            <input type="radio" value="0" name="status"/> 隐藏
                                        </li>

                                        <li><span>加入推荐:</span> 
                                            <input type="checkbox" value="1" name="is_best"/> 精品
                                            <input type="checkbox" value="1" name="is_hot"/> 热销
                                            <input type="checkbox" value="1" name="is_new"/> 新品
                                        </li>
                                    </ul>

                                </div>
                                <div role="tabpanel" class="tab-pane" id="detail">
                                    <textarea name="goods_content" id="goods_content" cols="100" rows="8"></textarea>

                                </div>
                                <div role="tabpanel" class="tab-pane" id="mix">

                                    <ul class="form_input">

                                        <li><span>商品颜色:</span>
                                            {{range $key, $value := .goodsColorList}}
                                                <input type="checkbox" name="goods_color" id="color_{{$value.Id}}"
                                                       value="{{$value.Id}}"/>
                                                <label for="color_{{$value.Id}}">{{$value.ColorName}}</label>
                                            {{end}}
                                        </li>

                                        <li><span>关联商品:</span>

                                            <input type="text" name="relation_goods" class="relation_goods"/> <i>填写关联商品的id
                                                多个以逗号隔开 格式:23,24,39</i>

                                        </li>

                                        <li><span>关联赠品:</span>

                                            <input type="text" name="goods_gift" class="goods_gift"/> <i>可为空
                                                格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i>

                                        </li>
                                        <li><span>关联配件:</span>

                                            <input type="text" name="goods_fitting" class="goods_fitting"/> <i>可为空
                                                格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i>

                                        </li>


                                        <li><span>更多属性:</span>
                                            <input type="text" name="goods_attr" class="goods_attr"/> <i> 格式:
                                                颜色:红色,白色,黄色 | 尺寸:41,42,43</i>
                                        </li>

                                        <li><span>SEO关键词:</span>

                                            <input type="text" name="goods_keywords" class="goods_keywords"/>

                                        </li>
                                        <li><span>SEO描述:</span>
                                            <textarea  name="goods_desc" class="goods_desc"  cols="100" rows="8"></textarea>

                                        </li>
                                    </ul>
                                </div>
                                <div role="tabpanel" class="tab-pane" id="attribute">

                                    <ul class="form_input">

                                        <li><span>商品类型: </span>
                                            <select name="goods_type_id" id="goods_type_id">
                                                <option value="0">--请选择商品类型--</option>
                                                {{range $key, $value := .goodsTypeList}}
                                                    <option value="{{$value.Id}}">{{$value.Title}}</option>
                                                {{end}}
                                            </select>

                                        </li>

                                    </ul>

                                    <ul class="form_input" id="goods_type_attribute">
                                    </ul>

                                </div>
                                <div role="tabpanel" class="tab-pane" id="photo">
                                    <div id="photoUploader"></div>
                                    <div id="photoList"></div>
                                </div>
                            </div>

                            <br/>
                            <button type="submit" class="btn btn-primary">提交</button>
                        </form>
                    </div>
                </div>


            </div>

        </div>
    </div>
    <script>
        、、富文本编辑器
        new FroalaEditor('#goods_content', {
            height: 300,
            language: "zh_cn", //需要引入语言包,注意名称
            // //自定义导航条
            // toolbarButtons: [
            //     ['bold', 'insertTable', 'html'],
            //     ['undo', 'redo']
            // ],
            // //< 768px使用
            // toolbarButtonsXS: [
            //     ['bold', 'strikethrough', 'subscript', 'superscript', 'outdent', 'indent',
            //     'clearFormatting', 'insertTable', 'html'],
            //     ['undo', 'redo']
            // ],
            //上传图片url
            imageUploadURL: '/admin/goods/editorImageUpload',
        });

        $(function () {
            //获取商品类型属性
            $("#goods_type_id").change(function () {
                var cateId = $(this).val()
                $.get("/admin/goods/goodsTypeAttribute", {"cateId": cateId}, function (response) {
                    var str = ""
                    if (response.success) {
                        var attrData = response.result;
                        for (var i = 0; i < attrData.length; i++) {
                            if (attrData[i].attr_type == 1) { //1 单行文本框
                                str += '<li><span>' + attrData[i].title + ':  </span> <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '" />   <input type="text" name="attr_value_list" /></li>'
                            } else if (attrData[i].attr_type == 2) { //2 多行文本框
                                str += '<li><span>' + attrData[i].title + ':  </span> <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '">  <textarea cols="50" rows="3" name="attr_value_list"></textarea></li>'
                            } else {
                                //3 从下面列表中选择(一行代表一个可选值)
                                var attrArray = attrData[i].attr_value.split("\n")
                                str += '<li><span>' + attrData[i].title + ':  </span>  <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '" />';
                                str += '<select name="attr_value_list">'
                                for (var j = 0; j < attrArray.length; j++) {
                                    str += '<option value="' + attrArray[j] + '">' + attrArray[j] + '</option>';
                                }
                                str += '</select>'
                                str += '</li>'
                            }
                        }
                        $("#goods_type_attribute").html(str);
                    }
                })
            })

            //批量上传图片,使用diyUpload上传图片插件
            $('#photoUploader').diyUpload({
                url:'/admin/goods/goodsImageUpload',
                success:function( response ) {
                    var photoStr='<input type="hidden" name="goods_image_list" value='+response.link+' />';
                    $("#photoList").append(photoStr)
                },
                error:function( err ) {
                    console.info( err );
                }
            });
        })
    </script>

    </body>
    </html>
{{end}}

edit.html

{{ define "admin/goods/edit.html" }}
{{ template "admin/public/page_header.html" .}}
<!-- 富文本编辑器 -->
<link href="/static/wysiwyg-editor/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/static/wysiwyg-editor/js/froala_editor.pkgd.min.js"></script>
<script type="text/javascript" src="/static/wysiwyg-editor/js/zh_cn.js"></script>
<!-- 上传图片的js css -->
<link rel="stylesheet" type="text/css" href="/static/diyUpload/css/webuploader.css">
<link rel="stylesheet" type="text/css" href="/static/diyUpload/css/diyUpload.css">
<script type="text/javascript" src="/static/diyUpload/js/webuploader.html5only.min.js"></script>
<script type="text/javascript" src="/static/diyUpload/js/diyUpload.js"></script>


<div class="container-fluid">
    <div class="row">
        <div class="panel panel-default">
            <div class="panel-heading">
                修改商品
            </div>
            <div class="panel-body">
                <div class="table-responsive goods-content input-form">
                    <form action="/admin/goods/doEdit" method="post" enctype="multipart/form-data">
                        <input type="hidden" name="id" class="input" value="{{.goods.Id}}" />
                        <input type="hidden" name="prePage" class="input" value="{{.prePage}}" />
                        <!-- Nav tabs -->
                        <ul class="nav nav-tabs" role="tablist">
                            <li role="presentation" class="active"><a href="#general" role="tab"
                                    data-toggle="tab">通用信息</a></li>
                            <li role="presentation"><a href="#detail" role="tab" data-toggle="tab">详细描述</a></li>
                            <li role="presentation"><a href="#mix" role="tab" data-toggle="tab">商品属性</a></li>
                            <li role="presentation"><a href="#attribute" role="tab" data-toggle="tab">规格与包装</a></li>
                            <li role="presentation"><a href="#photo" role="tab" data-toggle="tab">商品相册</a></li>
                        </ul>

                        <!-- Tab panes -->
                        <div class="tab-content">
                            <div role="tabpanel" class="tab-pane active" id="general">

                                <ul class="form_input">
                                    <li> <span> 商品标题:</span> <input type="text" name="title" class="input"
                                        value="{{.goods.Title}}" /></li>
                                    <li> <span> 附属标题:</span> <input type="text" name="sub_title" class="input"
                                            value="{{.goods.SubTitle}}" /></li>
                                    <li> <span> 商品版本:</span> <input type="text" name="goods_version" class="input"
                                            value="{{.goods.GoodsVersion}}" />
                                    </li>
                                    <li> <span>所属分类:</span>
                                        <select name="cate_id" id="cid">
                                            {{$cateId := .goods.CateId}}
                                            {{range $key,$value := .goodsCateList}}
                                                <option  {{if eq $cateId $value.Id}}selected{{end}} value="{{$value.Id}}" >{{$value.Title}}</option>
                                                {{range $k,$v := $value.GoodsCateItems}}
                                                     <option {{if eq $cateId $v.Id}}selected{{end}} value="{{$v.Id}}"> ----{{$v.Title}}</option>
                                                {{end}}
                                            {{end}}
                                        </select>
                                    </li>
                                    <li> <span> 商品图片:</span> 
                                        <input type="file" name="goods_img" />
                                        {{if ne .goods.GoodsImg ""}}
                                            <img src="{{.goods.GoodsImg | FormatImg}}" width="80"/>
                                        {{end}}
                                    </li>
                                  
                                    <li> <span>商品价格:</span> <input type="text" name="price" value="{{.goods.Price}}"/></li>
                                    <li> <span>商品原价:</span> <input type="text" name="market_price"  value="{{.goods.MarketPrice}}" /></li>
                                    <li> <span>商品库存:</span> <input type="text" name="goods_number" value="{{.goods.GoodsNumber}}" /></li>
                                    <li> <span>商品排序:</span> <input type="text" name="sort" value="{{.goods.Sort}}"/></li>

                                    <li> <span>商品状态:</span> 
                                        <input type="radio" value="1" {{if eq .goods.Status 1}}checked{{end}} name="status" checked /> 显示  
                                        <input type="radio" value="0" {{if eq .goods.Status 0}}checked{{end}} name="status" /> 隐藏
                                    </li>

                                    <li> <span>加入推荐:</span> 
                                        <input type="checkbox" value="1" name="is_best" {{if eq .goods.IsBest 1}}checked{{end}}/> 精品
                                        <input type="checkbox" value="1" name="is_hot" {{if eq .goods.IsHot 1}}checked{{end}}/> 热销
                                        <input type="checkbox" value="1" name="is_new" {{if eq .goods.IsNew 1}}checked{{end}} /> 新品
                                    </li>
                                </ul>

                            </div>
                            <div role="tabpanel" class="tab-pane" id="detail">

                                <textarea name="goods_content" id="content" cols="100" rows="8">{{.goods.GoodsContent}}</textarea>

                            </div>
                            <div role="tabpanel" class="tab-pane" id="mix">

                                <ul class="form_input">

                                    <li> <span>商品颜色:</span>

                                        {{range $key,$value := .goodsColorList}}
                                        <input type="checkbox" {{if eq $value.Checked true}}checked{{end}} value="{{$value.Id}}" name="goods_color"
                                            id="color_{{$value.Id}}" />
                                        <label for="color_{{$value.Id}}">{{$value.ColorName}}</label>
                                        &nbsp;
                                        {{end}}

                                    </li>

                                    <li> <span>关联商品:</span>

                                        <input type="text" name="relation_goods" class="relation_goods" value="{{.goods.RelationGoods}}"/> <i>填写关联商品的id
                                            多个以逗号隔开 格式:23,24,39</i>

                                    </li>

                                    <li> <span>关联赠品:</span>

                                        <input type="text" name="goods_gift" class="goods_gift" value="{{.goods.GoodsGift}}"/> <i>可为空 格式:23-2,39-5
                                            说明:例如23-2 中的23表示商品id,2表示商品数量</i>

                                    </li>
                                    <li> <span>关联配件:</span>

                                        <input type="text" name="goods_fitting" class="goods_fitting" value="{{.goods.GoodsFitting}}" /> <i>可为空
                                            格式:23-2,39-5 说明:例如23-2 中的23表示商品id,2表示商品数量</i>

                                    </li>


                                    <li> <span>更多属性:</span>

                                        <input type="text" name="goods_attr" class="goods_attr" value="{{.goods.GoodsAttr}}" /> <i> 格式: 颜色:红色,白色,黄色 |
                                            尺寸:41,42,43</i>

                                    </li>

                                    <li> <span>Seo关键词:</span>
                                                    
                                        <input type="text" name="goods_keywords" class="input" value="{{.goods.GoodsKeywords}}" />  
                                    
                                    </li>

                                    <li> <span>Seo描述:</span>                                                
                                        <textarea name="goods_desc" id="goods_desc" cols="100" rows="2">{{.goods.GoodsDesc}}</textarea>
                                    
                                    </li>




                                </ul>
                            </div>
                            <div role="tabpanel" class="tab-pane" id="attribute">

                                <ul class="form_input">

                                    <li> <span>商品类型: </span>
                                        <select name="goods_type_id" id="goods_type_id">
                                            <option value="0">--请选择商品类型--</option>
                                            {{$goodsTypeId := .goods.GoodsTypeId}}
                                            {{range $key,$value := .goodsTypeList}}
                                             <option  {{if eq $value.Id $goodsTypeId}}selected{{end}} value="{{$value.Id}}">{{$value.Title}}</option>
                                            {{end}}
                                        </select>

                                    </li>

                                </ul>

                                <ul class="form_input" id="goods_type_attribute">

                                    {{.goodsAttrStr | Str2Html}}
                                </ul>

                            </div>
                            <div role="tabpanel" class="tab-pane" id="photo">
                                <div id="photoList">
                                    <ul id="goods_image_list" class="goods_image_list clear">
                                        {{$goodsColor:=.goodsColorList}}
                                        {{range $key,$value := .goodsImageList}}
                                            <li>
                                                <img  src="{{$value.ImgUrl | FormatImg}}" class="pic" />
                                                <div class="color_list">
                                                    <select class="relation_goods_color" goods_image_id="{{$value.Id}}">
                                                        <option value="0">关联颜色</option>
                                                        {{range $k,$v := $goodsColor}}
                                                            {{if eq $v.Checked true}}                                                                    
                                                                <option value="{{$v.Id}}" {{if eq $value.ColorId $v.Id}}selected{{end}}>{{$v.ColorName}}</option>
                                                            {{end}}
                                                        {{end}}
                                                    </select>                                                        
                                                </div>    
                                                <div class="goods_image_delete" goods_image_id="{{$value.Id}}"></div>                                          
                                            </li>
                                        {{end}}
                                    </ul>
                                </div>

                                <div id="photoUploader"></div>
                               
                            </div>
                        </div>

                        <br />
                        <button type="submit" class="btn btn-primary">提交</button>
                    </form>
                </div>
            </div>


        </div>

    </div>
</div>
<script>
    new FroalaEditor('#content', {
        height: 300,
        language: 'zh_cn',  //要使用语言包首先需要引入 ,还要注意下划线
        // toolbarButtons: [ ['undo', 'redo'], ['bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'outdent', 'indent', 'clearFormatting', 'insertTable', 'html'] ],
        // toolbarButtonsXS: [ ['undo', 'redo'], ['bold', 'italic', 'underline'] ]
        imageUploadURL: '/admin/goods/editorImageUpload'
    });
    //获取商品类型属性
    $(function () {
        $("#goods_type_id").change(function () {
            var cateId = $(this).val()

            $.get("/admin/goods/goodsTypeAttribute", { "cateId": cateId }, function (response) {
                console.log(response)
                var str = ""
                if (response.success) {
                    var attrData = response.result;
                    for (var i = 0; i < attrData.length; i++) {
                        if (attrData[i].attr_type == 1) {
                            str += '<li><span>' + attrData[i].title + ':  </span> <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '" />   <input type="text" name="attr_value_list" /></li>'
                        } else if (attrData[i].attr_type == 2) {
                            str += '<li><span>' + attrData[i].title + ':  </span> <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '">  <textarea cols="50" rows="3" name="attr_value_list"></textarea></li>'
                        } else {
                            var attrArray = attrData[i].attr_value.split("\n")
                            str += '<li><span>' + attrData[i].title + ':  </span>  <input type="hidden" name="attr_id_list" value="' + attrData[i].id + '" />';
                            str += '<select name="attr_value_list">'
                            for (var j = 0; j < attrArray.length; j++) {
                                str += '<option value="' + attrArray[j] + '">' + attrArray[j] + '</option>';
                            }
                            str += '</select>'
                            str += '</li>'
                        }
                    }
                    $("#goods_type_attribute").html(str);
                }


            })
        })
    })

    //批量上传图片
    $(function () {
        $('#photoUploader').diyUpload({
            url: '/admin/goods/goodsImageUpload',
            success: function (response) {
                // console.info( data );
                var photoStr = '<input type="hidden" name="goods_image_list" value=' + response.link + ' />';
                $("#photoList").append(photoStr)
            },
            error: function (err) {
                console.info(err);
            }
        });

    })
    $(function(){
        $(".relation_goods_color").change(function(){
            var goods_image_id=$(this).attr("goods_image_id")
            var color_id=$(this).val()
            $.get("/admin/goods/changeGoodsImageColor",{"goods_image_id":goods_image_id,"color_id":color_id},function(response){
                if(response.success){
                    alert("操作成功")
                }
            })
        })

        $(".goods_image_delete").click(function(){
            var goods_image_id=$(this).attr("goods_image_id")   
            var _that=this;         
            var flag = confirm("确定要删除吗?");
            if (flag){
                $.get("/admin/goods/removeGoodsImage",{"goods_image_id":goods_image_id},function(response){
                    // console.log(response)
                    if(response.success){
                        //删除当前显示的图片
                        $(_that).parent().remove()
                    }
                })
            }
        })
    })
</script>
</body>

</html>
{{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 商城项目-导航模块功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试入门第一步:编写测试报告

什么是测试报告&#xff1f; 1、说明&#xff1a;是指把测试的过程和结果写成文档&#xff0c;对发现的问题和缺陷进行分析&#xff0c;为纠正软件的存在的质量问题提供依据&#xff0c;同时为软件验收和交付打下基础。 ps. 【测试过程和测试结果的分析报告&#xff0c;以及上线…

大爽pygame入门教程 第一节 基础知识 练习提示与答案

作者自我介绍&#xff1a;大爽歌, b站小UP主 &#xff0c;编程1对1辅导老师 1 逐行展示 思路提示 点击触发 之前的多行展示&#xff0c;是通过循环实现的。 这一回要点击触发一行的展示&#xff0c;不能直接使用循环了。 这里我们往更深层次去思考一下&#xff1a; 之前循环…

【CSS】margin 外边距负值使用案例 ( 正常外边距 | 使用外边距负值实现边框重叠 | 重叠边框突出显示 )

文章目录一、正常外边距案例二、使用外边距负值实现边框重叠三、重叠边框突出显示案例1、使用相对定位2、使用 z-index 设置定位盒子层级一、正常外边距案例 margine 正常情况下使用 , 设置 float 浮动 , 使得相邻的盒子模型紧贴在一起 ; 如果设置边框 , 则相邻的边框会重叠在…

A Comprehensive Capability Analysis of GPT-3 and GPT-3.5 Series Models论文学习

一、概述 Motivation&#xff1a;GPT系列的模型&#xff0c;像GPT-3&#xff0c;CodeX&#xff0c;InstructGPT&#xff0c;ChatGPT&#xff0c;尽管很多人关注他们能力的不同&#xff0c;但是很少关注GPT系列模型随着时间变化其能力的变化情况。 Methods&#xff1a; 在9个NL…

【LeetCode】轮转数组

&#x1f47b;内容专栏&#xff1a;《LeetCode刷题专栏》 &#x1f428;本文概括&#xff1a;189.轮转数组 &#x1f43c;本文作者&#xff1a;花 碟 &#x1f438;发布时间&#xff1a;2023.4.12 目录 思想1 暴力求解 代码实现&#xff1a; 思想2 三次倒置 代码实现&#…

Linux使用宝塔面板搭建网站,并内网穿透实现公网访问

文章目录前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4.固定http地址5. 配置二级子域名6.创建一个测试页面前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

Java阶段二Day02

Java阶段二Day02 文章目录Java阶段二Day02SpringMVC的部分主流程HTTP请求Request1&#xff1a;请求行2&#xff1a;消息头3&#xff1a;消息正文HTTP响应Response1&#xff1a;状态行2&#xff1a;响应头3&#xff1a;响应正文通过版本迭代仿写SpringBootV1BirdBootApplication…

云安全—etcd未授权漏洞cert证书遗失

0x00 前言 今儿来看看etcd未授权漏洞以及cert证书遗失问题 0x01 etcd简述 1.etcd是什么 etcd是采用go语言编写的一个分布式的key-value存储。 2.etcd作用 etcd主要用于解决集群管中的OS升级的分布式并发控制以及配置文件的存储与分发等问题。在kubernetes集群中&#xff…

《白帽子讲Web安全》世界观安全

1.Web安全简史1.1中国黑客简史对于现代计算机系统来说&#xff0c;在用户态的最高权限是root&#xff0c;也是黑客们最渴望能够获取的系统最高权限。不想拿到“root”的黑客&#xff0c;不是好黑客。在现实世界中&#xff0c;真正造成破坏的&#xff0c;往往并非那些挖掘并研究…

css右外边距失效问题解释以及解决办法

浏览器默认从左往右渲染元素&#xff0c;在没有超出父容器的宽度的前提下 如果子容器的宽度能够被容纳 设置margin-right是没有用的 解释 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title></title…

Linux环境搭建SVN服务器并实现公网访问 - cpolar端口映射

文章目录前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6.2 …

C/C++中文参考手册离线最新版

最近又用回C/C刷题&#xff0c;回想上一年还在用Java&#xff0c;C/C才是世界上最好的语言&#xff08;纯属调侃&#xff09;。哼哼&#xff0c;不许反驳。 想分享我正在使用的C/C中文参考手册离线最新版给大家&#xff0c;需要的朋友们可以自行下载&#xff08;free的哦&#…

Pytorch对预训练好的VGG16模型进行微调

目录 1.数据集准备、预训练模型准备 2.对VGG16模型进行微调 3.对数据集进行预处理 4.对模型进行训练并可视化训练过程 5.该测试案例的完整代码 对于一个复杂的卷积神经网络来说&#xff0c;通常网络的层数非常大&#xff0c;网络的深度非常深、网络的参数非常多&#xff0c…

中文翻译英语转换器-怎么把wps表格里的英文翻译成中文

对于那些需要频繁地进行中英互译的用户来说&#xff0c;字数限制是一个常见的问题。很多翻译软件经常会限制每次翻译的字数&#xff0c;导致用户翻译工作的效率和质量无法得到保证。如果您正在遭受这些限制&#xff0c;中英互译字数无限的软件将让您的翻译工作变得更加便捷和高…

反射之成员方法

Class类中用于获取成员方法的方法 Method[] getMethods(): 返回所有公共成员方法对象的数组&#xff0c;包括继承 Method[] getDeclaredMethods(): 返回所有成员方法对象的数组&#xff0c;不包括继承 Method getMethod(String name,Class ) …

笔记本硬盘坏了还能恢复数据吗 笔记本硬盘坏了怎么修复

笔记本电脑是经常使用的的学习、工作设备&#xff0c;它的硬盘中储存着大量的数据。一旦笔记本硬盘出现故障&#xff0c;这些数据会无法正常使用&#xff0c;对我们的学习生活产生重大影响。那么&#xff0c;笔记本硬盘坏了还能恢复数据吗&#xff0c;笔记本硬盘坏了怎么修复&a…

如何制作每日工作清单,让你高效完成开发工作

作为开发者&#xff0c;每天都有大量的任务需要完成。有时候&#xff0c;我们可能会感到无所适从&#xff0c;甚至失去动力。那么&#xff0c;如何有效地管理自己的任务和时间呢&#xff1f;在这篇文章中&#xff0c;我们将分享一位国外程序员大佬的亲身经验&#xff0c;介绍他…

【已解决】Field ‘id‘ doesn‘t have a default value 错误的解决办法

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

OpenLdap学习笔记3

1、进入容器&#xff1a; docker exec -it my-openldap-container /bin/bash2、在home目录下创建learn目录&#xff1a; CD /home mkdir learn 3、创建barbara.ldif文件&#xff1a; dn: cnbarbara,dcexample,dcorg objectClass: inetOrgPerson cn: barbara sn: Jensen titl…

优思学院|质量大师的那些名言(一)【质量是免费的】

名言是一种短小精悍、言简意赅的语言表达方式&#xff0c;它们通常包含着深刻的哲理和智慧&#xff0c;可以为我们提供指导和启示。 优思学院会在这个《质量大师的那些名言》系列中让大家透过那些名言&#xff0c;用最简单、直接&#xff0c;和深刻的方法来学习质量和六西格玛…