feat: remove shipping settings, use shipping templates for cart calculation
This commit is contained in:
@@ -258,3 +258,20 @@ func CalculateShippingFee(templateID *uint, weight float64, quantity int, subtot
|
|||||||
|
|
||||||
return template.FirstFee + additionalCount*template.AdditionalFee, nil
|
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}})
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ func SetupRoutes(r *gin.Engine) {
|
|||||||
api.GET("/settings/public", systemHandler.GetPublicSettings)
|
api.GET("/settings/public", systemHandler.GetPublicSettings)
|
||||||
api.GET("/payment-channels", paymentChannelHandler.PublicList)
|
api.GET("/payment-channels", paymentChannelHandler.PublicList)
|
||||||
api.POST("/payment/bepusdt/notify", paymentHandler.BepUsdtNotify)
|
api.POST("/payment/bepusdt/notify", paymentHandler.BepUsdtNotify)
|
||||||
|
api.POST("/shipping/calculate", shippingTemplateHandler.CalculateForUser)
|
||||||
|
|
||||||
authed := api.Group("")
|
authed := api.Group("")
|
||||||
authed.Use(middlewares.AuthMiddleware())
|
authed.Use(middlewares.AuthMiddleware())
|
||||||
|
|||||||
@@ -26,4 +26,13 @@ type UpdateShippingTemplateRequest struct {
|
|||||||
Provinces []string `json:"provinces"`
|
Provinces []string `json:"provinces"`
|
||||||
IsDefault *bool `json:"is_default"`
|
IsDefault *bool `json:"is_default"`
|
||||||
SortOrder *int `json:"sort_order"`
|
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"`
|
||||||
}
|
}
|
||||||
@@ -104,6 +104,10 @@ export const ticketApi = {
|
|||||||
reply: (id: number, data: any) => api.post(`/tickets/${id}/reply`, data),
|
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 = {
|
export const supplierApi = {
|
||||||
getOrders: () => api.get('/supplier/orders'),
|
getOrders: () => api.get('/supplier/orders'),
|
||||||
getOrderById: (id: number) => api.get(`/supplier/orders/${id}`),
|
getOrderById: (id: number) => api.get(`/supplier/orders/${id}`),
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
<el-tab-pane label="基础设置">
|
<el-tab-pane label="基础设置">
|
||||||
<el-form :model="settings" label-width="160px">
|
<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.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.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.tax_rate" :precision="2" /></el-form-item>
|
||||||
<el-form-item label="邀请奖励积分"><el-input-number v-model="settings.invite_credit_reward" /></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 { ElMessage } from 'element-plus'
|
||||||
import { adminApi } from '../../api'
|
import { adminApi } from '../../api'
|
||||||
|
|
||||||
const settings = reactive<Record<string, any>>({
|
const settings = reactive<Record<string, any>>({
|
||||||
payment_channel_fee_rate: 0,
|
payment_channel_fee_rate: 0,
|
||||||
shipping_fee_first_weight: 0,
|
|
||||||
shipping_fee_per_gram: 0,
|
|
||||||
service_fee_rate: 0,
|
service_fee_rate: 0,
|
||||||
tax_rate: 0,
|
tax_rate: 0,
|
||||||
invite_credit_reward: 0
|
invite_credit_reward: 0
|
||||||
})
|
})
|
||||||
const verifyEnabled = ref('true')
|
const verifyEnabled = ref('true')
|
||||||
const smtp = reactive({ smtp_host: '', smtp_port: '587', smtp_user: '', smtp_password: '', smtp_from: '' })
|
const smtp = reactive({ smtp_host: '', smtp_port: '587', smtp_user: '', smtp_password: '', smtp_from: '' })
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ import {
|
|||||||
Wallet, Coin, ChatDotRound, Delete
|
Wallet, Coin, ChatDotRound, Delete
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { useCartStore } from '../../store/cart'
|
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 { getFirstImage } from '../../utils/image'
|
||||||
import BackNav from '../../components/BackNav.vue'
|
import BackNav from '../../components/BackNav.vue'
|
||||||
|
|
||||||
@@ -346,12 +346,78 @@ const selectedWeight = computed(() => {
|
|||||||
.reduce((sum, item) => sum + (item.product?.weight || 0) * item.quantity, 0)
|
.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 shippingFee = ref(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)
|
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 serviceFee = computed(() => {
|
||||||
const rate = parseFloat(settings.value.service_fee_rate || '0')
|
const rate = parseFloat(settings.value.service_fee_rate || '0')
|
||||||
@@ -379,6 +445,11 @@ const canCheckout = computed(() => {
|
|||||||
return selectedItems.every(item => item.product?.is_active)
|
return selectedItems.every(item => item.product?.is_active)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听选中商品和地址变化,重新计算运费
|
||||||
|
watch([selectedIds, selectedAddressId, items], () => {
|
||||||
|
calculateShippingFee()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
async function fetchSettings() {
|
async function fetchSettings() {
|
||||||
try {
|
try {
|
||||||
const res: any = await systemApi.getPublicSettings()
|
const res: any = await systemApi.getPublicSettings()
|
||||||
|
|||||||
Reference in New Issue
Block a user