feat: remove shipping settings, use shipping templates for cart calculation
Build and Push Docker Image / build-and-push (push) Successful in 1m0s
Build and Push Docker Image / deploy (push) Successful in 7s

This commit is contained in:
nuyue
2026-07-12 20:01:03 +08:00
parent d7716b463b
commit bdefa34d41
6 changed files with 112 additions and 14 deletions
@@ -258,3 +258,20 @@ func CalculateShippingFee(templateID *uint, weight float64, quantity int, subtot
return template.FirstFee + additionalCount*template.AdditionalFee, nil
}
// CalculateForUser 用户端计算运费接口
func (h *ShippingTemplateHandler) CalculateForUser(c *gin.Context) {
var req schemas.CalculateShippingRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fee, err := CalculateShippingFee(req.TemplateID, req.Weight, req.Quantity, req.Subtotal, req.Province)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "计算运费失败"})
return
}
c.JSON(http.StatusOK, gin.H{"data": gin.H{"shipping_fee": fee}})
}
+1
View File
@@ -72,6 +72,7 @@ func SetupRoutes(r *gin.Engine) {
api.GET("/settings/public", systemHandler.GetPublicSettings)
api.GET("/payment-channels", paymentChannelHandler.PublicList)
api.POST("/payment/bepusdt/notify", paymentHandler.BepUsdtNotify)
api.POST("/shipping/calculate", shippingTemplateHandler.CalculateForUser)
authed := api.Group("")
authed.Use(middlewares.AuthMiddleware())
@@ -26,4 +26,13 @@ type UpdateShippingTemplateRequest struct {
Provinces []string `json:"provinces"`
IsDefault *bool `json:"is_default"`
SortOrder *int `json:"sort_order"`
}
// CalculateShippingRequest 计算运费请求
type CalculateShippingRequest struct {
TemplateID *uint `json:"template_id"`
Weight float64 `json:"weight"`
Quantity int `json:"quantity"`
Subtotal float64 `json:"subtotal"`
Province string `json:"province"`
}
+4
View File
@@ -104,6 +104,10 @@ export const ticketApi = {
reply: (id: number, data: any) => api.post(`/tickets/${id}/reply`, data),
}
export const shippingApi = {
calculate: (data: any) => api.post('/shipping/calculate', data),
}
export const supplierApi = {
getOrders: () => api.get('/supplier/orders'),
getOrderById: (id: number) => api.get(`/supplier/orders/${id}`),
+3 -7
View File
@@ -5,8 +5,6 @@
<el-tab-pane label="基础设置">
<el-form :model="settings" label-width="160px">
<el-form-item label="渠道费率 (%)"><el-input-number v-model="settings.payment_channel_fee_rate" :precision="2" /></el-form-item>
<el-form-item label="首重运费"><el-input-number v-model="settings.shipping_fee_first_weight" :precision="2" /></el-form-item>
<el-form-item label="续重单价"><el-input-number v-model="settings.shipping_fee_per_gram" :precision="2" /></el-form-item>
<el-form-item label="服务费率 (%)"><el-input-number v-model="settings.service_fee_rate" :precision="2" /></el-form-item>
<el-form-item label="税费 (%)"><el-input-number v-model="settings.tax_rate" :precision="2" /></el-form-item>
<el-form-item label="邀请奖励积分"><el-input-number v-model="settings.invite_credit_reward" /></el-form-item>
@@ -34,13 +32,11 @@ import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { adminApi } from '../../api'
const settings = reactive<Record<string, any>>({
payment_channel_fee_rate: 0,
shipping_fee_first_weight: 0,
shipping_fee_per_gram: 0,
const settings = reactive<Record<string, any>>({
payment_channel_fee_rate: 0,
service_fee_rate: 0,
tax_rate: 0,
invite_credit_reward: 0
invite_credit_reward: 0
})
const verifyEnabled = ref('true')
const smtp = reactive({ smtp_host: '', smtp_port: '587', smtp_user: '', smtp_password: '', smtp_from: '' })
+78 -7
View File
@@ -273,7 +273,7 @@ import {
Wallet, Coin, ChatDotRound, Delete
} from '@element-plus/icons-vue'
import { useCartStore } from '../../store/cart'
import { systemApi, addressApi, orderApi, paymentChannelApi, userApi } from '../../api'
import { systemApi, addressApi, orderApi, paymentChannelApi, userApi, shippingApi } from '../../api'
import { getFirstImage } from '../../utils/image'
import BackNav from '../../components/BackNav.vue'
@@ -346,12 +346,78 @@ const selectedWeight = computed(() => {
.reduce((sum, item) => sum + (item.product?.weight || 0) * item.quantity, 0)
})
const shippingFee = computed(() => {
const firstWeight = parseFloat(settings.value.shipping_fee_first_weight || '0')
const perGram = parseFloat(settings.value.shipping_fee_per_gram || '0')
if (selectedTotal.value >= 99 || firstWeight === 0) return 0
return firstWeight + perGram * Math.max(0, selectedWeight.value - 500)
})
// 运费(使用运费模板计算)
const shippingFee = ref(0)
// 计算运费
async function calculateShippingFee() {
if (selectedIds.value.length === 0 || !selectedAddressId.value) {
shippingFee.value = 0
return
}
const selectedItems = items.value.filter(item => selectedIds.value.includes(item.id))
const selectedAddress = addresses.value.find(a => a.id === selectedAddressId.value)
if (!selectedAddress) {
shippingFee.value = 0
return
}
// 按运费模板分组计算
const templateGroups: Map<number, { weight: number, quantity: number, subtotal: number }> = new Map()
let noTemplateWeight = 0, noTemplateQuantity = 0, noTemplateSubtotal = 0
for (const item of selectedItems) {
const templateId = item.product?.shipping_template_id
const weight = (item.product?.weight || 0) * item.quantity
const subtotal = (item.product?.price || 0) * item.quantity
if (templateId) {
if (!templateGroups.has(templateId)) {
templateGroups.set(templateId, { weight: 0, quantity: 0, subtotal: 0 })
}
const group = templateGroups.get(templateId)!
group.weight += weight
group.quantity += item.quantity
group.subtotal += subtotal
} else {
noTemplateWeight += weight
noTemplateQuantity += item.quantity
noTemplateSubtotal += subtotal
}
}
let totalFee = 0
// 计算各模板的运费
for (const [templateId, data] of templateGroups) {
try {
const res: any = await shippingApi.calculate({
template_id: templateId,
weight: data.weight,
quantity: data.quantity,
subtotal: data.subtotal,
province: selectedAddress.province
})
totalFee += res.data?.shipping_fee || 0
} catch {}
}
// 没有模板的商品使用默认模板
if (noTemplateSubtotal > 0) {
try {
const res: any = await shippingApi.calculate({
weight: noTemplateWeight,
quantity: noTemplateQuantity,
subtotal: noTemplateSubtotal,
province: selectedAddress.province
})
totalFee += res.data?.shipping_fee || 0
} catch {}
}
shippingFee.value = totalFee
}
const serviceFee = computed(() => {
const rate = parseFloat(settings.value.service_fee_rate || '0')
@@ -379,6 +445,11 @@ const canCheckout = computed(() => {
return selectedItems.every(item => item.product?.is_active)
})
// 监听选中商品和地址变化,重新计算运费
watch([selectedIds, selectedAddressId, items], () => {
calculateShippingFee()
}, { deep: true })
async function fetchSettings() {
try {
const res: any = await systemApi.getPublicSettings()