分类封面

dev
truthhun 2 years ago
parent 590f4688c7
commit a4b0442d59

@ -19,6 +19,7 @@ message Category {
int32 doc_count = 4;
int32 sort = 5;
bool enable = 6;
string cover = 9; //
google.protobuf.Timestamp created_at = 7 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp updated_at = 8 [ (gogoproto.stdtime) = true ];
}

@ -279,7 +279,7 @@ func (s *AttachmentAPIService) UploadBanner(ctx *gin.Context) {
}
// 上传文档分类封面
func (s *AttachmentAPIService) UploadCategoryCover(ctx *gin.Context) {
func (s *AttachmentAPIService) UploadCategory(ctx *gin.Context) {
s.uploadImage(ctx, model.AttachmentTypeCategoryCover)
}

@ -146,10 +146,28 @@ func (m *DBModel) GetArticleList(opt *OptionGetArticleList) (articleList []Artic
// DeleteArticle 删除数据
func (m *DBModel) DeleteArticle(ids []int64) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Article{}).Error
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
err = sess.Where("id in (?)", ids).Delete(&Article{}).Error
if err != nil {
m.logger.Error("DeleteArticle", zap.Error(err))
return
}
// 附件标记删除将type_id设置为0
err = sess.Model(&Attachment{}).Where("type = ? and type_id in (?)", AttachmentTypeArticle, ids).Update("type_id", 0).Error
if err != nil {
m.logger.Error("DeleteArticle", zap.Error(err))
return
}
return
}

@ -1,6 +1,8 @@
package model
import (
"path/filepath"
"strings"
"time"
"go.uber.org/zap"
@ -172,3 +174,19 @@ func (m *DBModel) DeleteAttachment(ids []int64) (err error) {
}
return
}
func (m *DBModel) setAttachmentType(attachmentType int, attachmentTypeId int64, paths ...string) {
var hashes []string
for _, path := range paths {
if strings.HasPrefix(strings.TrimLeft(path, "."), "/uploads/") {
filename := filepath.Base(path)
hashes = append(hashes, strings.TrimSuffix(filename, filepath.Ext(filename)))
}
}
if len(hashes) > 0 {
err := m.db.Model(&Attachment{}).Where("hash in (?) and type = ? and type_id = 0", hashes, attachmentType).Update("type_id", attachmentTypeId).Error
if err != nil {
m.logger.Error("setAttachmentType", zap.Error(err))
}
}
}

@ -25,7 +25,6 @@ func (Banner) TableName() string {
}
// CreateBanner 创建Banner
// TODO: 创建成功之后,注意相关表统计字段数值的增减
func (m *DBModel) CreateBanner(banner *Banner) (err error) {
err = m.db.Create(banner).Error
if err != nil {
@ -112,9 +111,27 @@ func (m *DBModel) GetBannerList(opt *OptionGetBannerList) (bannerList []Banner,
// DeleteBanner 删除数据
func (m *DBModel) DeleteBanner(ids []int64) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Banner{}).Error
sess := m.db.Begin()
defer func() {
if err != nil {
sess.Rollback()
} else {
sess.Commit()
}
}()
err = sess.Where("id in (?)", ids).Delete(&Banner{}).Error
if err != nil {
m.logger.Error("DeleteBanner", zap.Error(err))
return
}
// 附件,标记删除
err = sess.Model(&Attachment{}).Where("type = ? and type_id in (?)", AttachmentTypeBanner, ids).Update("type_id", 0).Error
if err != nil {
m.logger.Error("DeleteBanner", zap.Error(err))
return
}
return
}

@ -9,6 +9,7 @@ import (
type Category struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Cover string `form:"cover" json:"cover,omitempty" gorm:"column:cover;comment:分类封面;type:varchar(255);size:255;"` //
ParentId int `form:"parent_id" json:"parent_id,omitempty" gorm:"column:parent_id;type:int(11);size:11;default:0;index:parent_id_title,unique;index:parent_id;comment:上级ID;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(64);size:64;index:parent_id_title,unique;comment:分类名称;"`
DocCount int `form:"doc_count" json:"doc_count,omitempty" gorm:"column:doc_count;type:int(11);size:11;default:0;comment:文档统计;"`
@ -23,13 +24,15 @@ func (Category) TableName() string {
}
// CreateCategory 创建Category
// TODO: 创建成功之后,注意相关表统计字段数值的增减
func (m *DBModel) CreateCategory(category *Category) (err error) {
err = m.db.Create(category).Error
if err != nil {
m.logger.Error("CreateCategory", zap.Error(err))
return
}
if category.Cover != "" {
m.setAttachmentType(AttachmentTypeCategoryCover, category.Id, category.Cover)
}
return
}
@ -48,6 +51,11 @@ func (m *DBModel) UpdateCategory(category *Category, updateFields ...string) (er
if err != nil {
m.logger.Error("UpdateCategory", zap.Error(err))
}
if category.Cover != "" {
m.setAttachmentType(AttachmentTypeCategoryCover, category.Id, category.Cover)
}
return
}

@ -301,6 +301,14 @@ func (m *DBModel) DeleteDocument(ids []int64, deletedUserId int64, deepDelete ..
}
}
if len(deepDelete) > 0 && deepDelete[0] { // 彻底删除
err = sess.Model(&Attachment{}).Where("type = ? and type_id in (?)", AttachmentTypeDocument, ids).Update("type_id", 0).Error
if err != nil {
m.logger.Error("DeleteDocument", zap.Error(err))
return
}
}
return
}

@ -24,7 +24,7 @@ func RegisterGinRouter(app *gin.Engine, dbModel *model.DBModel, logger *zap.Logg
checkPermissionGroup.POST("avatar", attachmentAPIService.UploadAvatar)
checkPermissionGroup.POST("banner", attachmentAPIService.UploadBanner)
checkPermissionGroup.POST("document", attachmentAPIService.UploadDocument)
checkPermissionGroup.POST("category/cover", attachmentAPIService.UploadCategoryCover)
checkPermissionGroup.POST("category", attachmentAPIService.UploadCategory)
checkPermissionGroup.POST("article", attachmentAPIService.UploadArticle)
}

@ -2,11 +2,15 @@ package util
import (
"context"
"errors"
"fmt"
"image"
"io"
"os"
"path/filepath"
"strings"
"github.com/disintegration/imaging"
jsoniter "github.com/json-iterator/go"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
@ -46,6 +50,24 @@ func GetGRPCRemoteIP(ctx context.Context) (ips []string, err error) {
return
}
// 图片缩放居中裁剪
func CropImage(file string, width, height int) (err error) {
var img image.Image
img, err = imaging.Open(file)
if err != nil {
return
}
ext := strings.ToLower(filepath.Ext(file))
switch ext {
case ".jpeg", ".jpg", ".png", ".gif":
img = imaging.Fill(img, width, height, imaging.Center, imaging.CatmullRom)
default:
err = errors.New("unsupported image format")
return
}
return imaging.Save(img, file)
}
// LimitMin 数字最小值限制
func LimitMin(number int, minValue int) int {
if number >= minValue {

@ -21,6 +21,24 @@
placeholder="请选择上级分类"
></el-cascader>
</el-form-item>
<!-- 创建的时候不支持上传封面因为创建的时候支持批量创建 -->
<el-form-item
v-if="
category.id > 0 &&
(!category.parent_id ||
category.parent_id === 0 ||
category.parent_id.length === 0)
"
label="分类封面(一级分类才需要上传)"
class="form-item-cover"
>
<UploadImage
:action="'/api/v1/upload/category'"
:image="category.cover"
:width="'180px'"
@success="successUpload"
/>
</el-form-item>
<el-form-item
label="名称"
prop="title"
@ -77,9 +95,13 @@
</div>
</template>
<script>
import UploadImage from '~/components/UploadImage.vue'
import { createCategory, updateCategory } from '~/api/category'
export default {
name: 'FormCategory',
components: {
UploadImage,
},
props: {
initCategory: {
type: Object,
@ -102,6 +124,7 @@ export default {
title: '',
sort: 0,
enable: false,
cover: '',
},
}
},
@ -109,13 +132,14 @@ export default {
initCategory: {
handler(val) {
if (!val.sort) val.sort = 0
if (!val.cover) val.cover = ''
this.category = val
},
immediate: true,
},
},
created() {
this.category = { title: '', sort: 0, ...this.initCategory }
this.category = { title: '', cover: '', sort: 0, ...this.initCategory }
},
methods: {
onSubmit() {
@ -135,6 +159,10 @@ export default {
}
if (this.category.id > 0) {
if (category.parent_id > 0 || category.parent_id.length > 0) {
category.cover = ''
}
const res = await updateCategory(category)
if (res.status === 200) {
this.$message.success('修改成功')
@ -160,12 +188,24 @@ export default {
this.$refs.formCategory.clearValidate()
},
resetFields() {
this.$refs.formCategory.resetFields()
this.category = { title: '', cover: '', sort: 0, ...this.initCategory }
},
reset() {
this.resetFields()
this.clearValidate()
},
successUpload(res) {
this.category.cover = res.data.path
},
},
}
</script>
<style lang="scss">
.com-form-category {
.form-item-cover {
.el-form-item__content {
line-height: 1;
}
}
}
</style>

@ -71,8 +71,16 @@
<i class="el-icon-link"></i> {{ scope.row[item.prop] }}</a
>
</span>
<span v-else-if="item.type === 'banner'">
<UploadImage :disabled="true" :image="scope.row[item.prop]" />
<span v-else-if="item.type === 'image'">
<!-- 因为table cell有个左右的10px内边距所以需要调整下 -->
<UploadImage
v-if="scope.row[item.prop]"
:disabled="true"
:image="scope.row[item.prop]"
:width="item.width ? item.width + 'px' : 'auto'"
style="margin-left: -10px; margin-right: -10px"
/>
<span v-else>-</span>
</span>
<span v-else-if="item.type === 'array'">
<template v-if="scope.row[item.prop]">

@ -6,6 +6,7 @@
:headers="{ authorization: `bearer ${token}` }"
:show-file-list="false"
:on-success="success"
:on-error="onError"
accept="image/jpeg,image/png,image/gif,image/jpg"
:multiple="false"
:disabled="disabled"
@ -71,6 +72,24 @@ export default {
success(res) {
this.$emit('success', res)
},
onError(err) {
try {
const message = JSON.parse(err.message)
this.$message.error(message.message)
} catch (e) {
this.$message.error(err)
}
},
},
}
</script>
<style lang="scss">
.com-upload-image {
.el-image {
img {
width: 100%;
height: auto;
}
}
}
</style>

@ -216,7 +216,7 @@ export default {
})
this.tableListFields = [
{ prop: 'id', label: 'ID', width: 80, type: 'number' },
{ prop: 'path', label: '横幅', width: 360, type: 'banner' },
{ prop: 'path', label: '横幅', width: 360, type: 'image' },
{
prop: 'type',
label: '类型',

@ -66,7 +66,7 @@ export default {
searchFormFields: [],
tableListFields: [],
selectedRow: [],
category: { id: 0 },
category: { id: 0, title: '', cover: '', sort: '' },
}
},
async created() {
@ -106,13 +106,20 @@ export default {
this.listCategory()
},
onCreate() {
this.category = { id: 0, enable: true }
this.category = {
id: 0,
enable: true,
title: '',
cover: '',
parent_id: 0,
sort: 0,
}
this.formVisible = true
},
async editRow(row) {
const res = await getCategory({ id: row.id })
if (res.status === 200) {
this.category = res.data
this.category = { cover: '', ...res.data }
this.formVisible = true
} else {
this.$message.error(res.data.message || '查询失败')
@ -206,6 +213,7 @@ export default {
type: 'number',
fixed: 'left',
},
{ prop: 'cover', label: '封面', width: 100, type: 'image' },
{ prop: 'doc_count', label: '文档数', width: 80, type: 'number' },
{ prop: 'id', label: 'ID', width: 80, type: 'number' },
{ prop: 'created_at', label: '创建时间', width: 160, type: 'datetime' },

Loading…
Cancel
Save