feat: configure member dispatch delay
This commit is contained in:
@@ -82,6 +82,15 @@ func (s *sSysMemberLevel) View(ctx context.Context, in *sysin.MemberLevelViewInp
|
||||
}
|
||||
|
||||
func (s *sSysMemberLevel) Save(ctx context.Context, in *sysin.MemberLevelSaveInp) (id, versionId uint64, err error) {
|
||||
rules := g.Map{}
|
||||
if in.RuleJson != nil {
|
||||
for key, value := range in.RuleJson.Map() {
|
||||
rules[key] = value
|
||||
}
|
||||
}
|
||||
rules["auto_order_delay_min_seconds"] = in.AutoOrderDelayMinSeconds
|
||||
rules["auto_order_delay_max_seconds"] = in.AutoOrderDelayMaxSeconds
|
||||
in.RuleJson = gjson.New(rules)
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
lc, vc := dao.AppMemberLevel.Columns(), dao.AppMemberLevelVersion.Columns()
|
||||
count, e := s.model(ctx).Where(lc.LevelCode, in.LevelCode).WhereNot(lc.Id, in.Id).Count()
|
||||
@@ -253,7 +262,12 @@ func (s *sSysMemberLevel) Rollback(ctx context.Context, in *sysin.MemberLevelRol
|
||||
if level == nil {
|
||||
return 0, gerror.New("会员等级不存在")
|
||||
}
|
||||
_, versionId, err = s.Save(ctx, &sysin.MemberLevelSaveInp{Id: level.Id, LevelCode: level.LevelCode, Name: level.Name, IconAttachmentId: level.IconAttachmentId, IconUrl: level.IconUrl, UpgradeAmount: source.UpgradeAmount, GiftAmount: source.GiftAmount, DailyOrderLimit: source.DailyOrderLimit, CommissionRate: source.CommissionRate, DailyWithdrawLimit: source.DailyWithdrawLimit, RuleJson: source.RuleJson, EffectiveAt: nil, Sort: level.Sort, Status: level.Status, Remark: "回滚自 V" + fmt.Sprint(source.VersionNo) + ":" + in.Reason})
|
||||
delayMin, delayMax := 0, 0
|
||||
if source.RuleJson != nil {
|
||||
delayMin = source.RuleJson.Get("auto_order_delay_min_seconds").Int()
|
||||
delayMax = source.RuleJson.Get("auto_order_delay_max_seconds").Int()
|
||||
}
|
||||
_, versionId, err = s.Save(ctx, &sysin.MemberLevelSaveInp{Id: level.Id, LevelCode: level.LevelCode, Name: level.Name, IconAttachmentId: level.IconAttachmentId, IconUrl: level.IconUrl, UpgradeAmount: source.UpgradeAmount, GiftAmount: source.GiftAmount, DailyOrderLimit: source.DailyOrderLimit, CommissionRate: source.CommissionRate, DailyWithdrawLimit: source.DailyWithdrawLimit, AutoOrderDelayMinSeconds: delayMin, AutoOrderDelayMaxSeconds: delayMax, RuleJson: source.RuleJson, EffectiveAt: nil, Sort: level.Sort, Status: level.Status, Remark: "回滚自 V" + fmt.Sprint(source.VersionNo) + ":" + in.Reason})
|
||||
return
|
||||
}
|
||||
func (s *sSysMemberLevel) History(ctx context.Context, in *sysin.MemberLevelHistoryInp) (list []*entity.AppMemberLevelVersion, total int, err error) {
|
||||
|
||||
@@ -55,23 +55,25 @@ type MemberLevelViewModel struct {
|
||||
Published *entity.AppMemberLevelVersion `json:"published"`
|
||||
}
|
||||
type MemberLevelSaveInp struct {
|
||||
Id uint64 `json:"id"`
|
||||
VersionId uint64 `json:"versionId"`
|
||||
Version uint `json:"version"`
|
||||
LevelCode string `json:"levelCode" v:"required|max-length:32#等级编码不能为空|等级编码不能超过32个字符"`
|
||||
Name string `json:"name" v:"required|max-length:64#等级名称不能为空|等级名称不能超过64个字符"`
|
||||
IconAttachmentId uint64 `json:"iconAttachmentId"`
|
||||
IconUrl string `json:"iconUrl" v:"max-length:1000#图标地址不能超过1000个字符"`
|
||||
UpgradeAmount int64 `json:"upgradeAmount" v:"min:0#升级金额不能小于0"`
|
||||
GiftAmount int64 `json:"giftAmount" v:"min:0#赠送金额不能小于0"`
|
||||
DailyOrderLimit uint `json:"dailyOrderLimit"`
|
||||
CommissionRate uint `json:"commissionRate" v:"max:10000#佣金比例不能超过100%"`
|
||||
DailyWithdrawLimit uint `json:"dailyWithdrawLimit"`
|
||||
RuleJson *gjson.Json `json:"ruleJson"`
|
||||
EffectiveAt *gtime.Time `json:"effectiveAt"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status" d:"1"`
|
||||
Remark string `json:"remark" v:"max-length:500#备注不能超过500个字符"`
|
||||
Id uint64 `json:"id"`
|
||||
VersionId uint64 `json:"versionId"`
|
||||
Version uint `json:"version"`
|
||||
LevelCode string `json:"levelCode" v:"required|max-length:32#等级编码不能为空|等级编码不能超过32个字符"`
|
||||
Name string `json:"name" v:"required|max-length:64#等级名称不能为空|等级名称不能超过64个字符"`
|
||||
IconAttachmentId uint64 `json:"iconAttachmentId"`
|
||||
IconUrl string `json:"iconUrl" v:"max-length:1000#图标地址不能超过1000个字符"`
|
||||
UpgradeAmount int64 `json:"upgradeAmount" v:"min:0#升级金额不能小于0"`
|
||||
GiftAmount int64 `json:"giftAmount" v:"min:0#赠送金额不能小于0"`
|
||||
DailyOrderLimit uint `json:"dailyOrderLimit"`
|
||||
CommissionRate uint `json:"commissionRate" v:"max:10000#佣金比例不能超过100%"`
|
||||
DailyWithdrawLimit uint `json:"dailyWithdrawLimit"`
|
||||
AutoOrderDelayMinSeconds int `json:"autoOrderDelayMinSeconds" v:"min:0#最短派单时间不能小于0"`
|
||||
AutoOrderDelayMaxSeconds int `json:"autoOrderDelayMaxSeconds" v:"min:0#最长派单时间不能小于0"`
|
||||
RuleJson *gjson.Json `json:"ruleJson"`
|
||||
EffectiveAt *gtime.Time `json:"effectiveAt"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status" d:"1"`
|
||||
Remark string `json:"remark" v:"max-length:500#备注不能超过500个字符"`
|
||||
}
|
||||
|
||||
func (in *MemberLevelSaveInp) Filter(ctx context.Context) error {
|
||||
@@ -81,6 +83,9 @@ func (in *MemberLevelSaveInp) Filter(ctx context.Context) error {
|
||||
if in.UpgradeAmount > 0 && in.GiftAmount > in.UpgradeAmount {
|
||||
return gerror.New("赠送金额不能超过升级金额")
|
||||
}
|
||||
if in.AutoOrderDelayMaxSeconds < in.AutoOrderDelayMinSeconds {
|
||||
return gerror.New("最长派单时间不能小于最短派单时间")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 会员等级自动派单随机等待区间,默认与原型规则一致:15-20 分钟。
|
||||
UPDATE `hg_app_member_level_version`
|
||||
SET `rule_json` = JSON_SET(
|
||||
COALESCE(`rule_json`, JSON_OBJECT()),
|
||||
'$.auto_order_delay_min_seconds', COALESCE(JSON_EXTRACT(`rule_json`, '$.auto_order_delay_min_seconds'), 900),
|
||||
'$.auto_order_delay_max_seconds', COALESCE(JSON_EXTRACT(`rule_json`, '$.auto_order_delay_max_seconds'), 1200)
|
||||
)
|
||||
WHERE JSON_EXTRACT(`rule_json`, '$.auto_order_delay_min_seconds') IS NULL
|
||||
OR JSON_EXTRACT(`rule_json`, '$.auto_order_delay_max_seconds') IS NULL;
|
||||
@@ -6,7 +6,7 @@
|
||||
<BasicTable ref="actionRef" :columns="columns" :request="loadDataTable" :row-key="r=>r.id" :action-column="actionColumn" :scroll-x="scrollX" :resize-height-offset="-10000"><template #tableTitle><n-button v-if="hasPermission(['/memberLevel/save'])" type="primary" @click="openEdit()">新增等级</n-button></template></BasicTable>
|
||||
</n-card>
|
||||
<n-modal v-model:show="showEdit" preset="card" :title="form.id?'编辑等级':'新增等级'" class="level-modal">
|
||||
<n-alert type="info" :show-icon="false">金额以元录入,服务端按分保存;佣金以百分比录入,服务端按万分比保存。</n-alert>
|
||||
<n-alert type="info" :show-icon="false">金额以元录入,服务端按分保存;佣金以百分比录入;自动派单会在所设时间范围内随机生成倒计时。</n-alert>
|
||||
<n-form ref="formRef" :model="form" :rules="rules" label-placement="left" label-width="110">
|
||||
<n-grid :cols="2" :x-gap="16">
|
||||
<n-form-item-gi label="等级编码" path="levelCode" required><n-input v-model:value="form.levelCode" placeholder="如 VIP1" /></n-form-item-gi>
|
||||
@@ -18,6 +18,8 @@
|
||||
<n-form-item-gi label="每日接单次数"><n-input-number v-model:value="form.dailyOrderLimit" :min="0" /></n-form-item-gi>
|
||||
<n-form-item-gi label="佣金比例(%)"><n-input-number v-model:value="form.commissionPercent" :min="0" :max="100" :precision="2" /></n-form-item-gi>
|
||||
<n-form-item-gi label="每日提现次数"><n-input-number v-model:value="form.dailyWithdrawLimit" :min="0" /></n-form-item-gi>
|
||||
<n-form-item-gi label="最短派单(分钟)" path="autoOrderDelayMinMinutes"><n-input-number v-model:value="form.autoOrderDelayMinMinutes" :min="0" :precision="0" /></n-form-item-gi>
|
||||
<n-form-item-gi label="最长派单(分钟)" path="autoOrderDelayMaxMinutes"><n-input-number v-model:value="form.autoOrderDelayMaxMinutes" :min="0" :precision="0" /></n-form-item-gi>
|
||||
<n-form-item-gi label="排序"><n-input-number v-model:value="form.sort" /></n-form-item-gi>
|
||||
<n-form-item-gi label="备注" :span="2"><n-input v-model:value="form.remark" type="textarea" /></n-form-item-gi>
|
||||
</n-grid>
|
||||
@@ -28,11 +30,11 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed,h,onMounted,reactive,ref } from 'vue';import { useDialog,useMessage } from 'naive-ui';import { BasicTable,TableAction } from '@/components/Table';import { BasicForm,useForm } from '@/components/Form';import { usePermission } from '@/hooks/web/usePermission';import { useDictStore } from '@/store/modules/dict';import { adaTableScrollX } from '@/utils/hotgo';import { List,View,Save,Status,Delete } from '@/api/memberLevel';import { columns,schemas,loadOptions } from './model';
|
||||
const message=useMessage(),dialog=useDialog(),dict=useDictStore();const{hasPermission}=usePermission();const actionRef=ref(),searchFormRef=ref<any>({}),formRef=ref<any>(),showEdit=ref(false),saving=ref(false);const form=reactive<any>({});const rules={levelCode:{required:true,message:'请输入等级编码',trigger:['input','blur']},name:{required:true,message:'请输入等级名称',trigger:['input','blur']}};const [register]=useForm({gridProps:{cols:'1 s:1 m:2 l:3'},labelWidth:80,schemas});const loadDataTable=res=>List({...searchFormRef.value?.formModel,...res});const reloadTable=()=>actionRef.value?.reload();const reset=(o,v)=>{Object.keys(o).forEach(k=>delete o[k]);Object.assign(o,v)};
|
||||
const message=useMessage(),dialog=useDialog(),dict=useDictStore();const{hasPermission}=usePermission();const actionRef=ref(),searchFormRef=ref<any>({}),formRef=ref<any>(),showEdit=ref(false),saving=ref(false);const form=reactive<any>({});const rules={levelCode:{required:true,message:'请输入等级编码',trigger:['input','blur']},name:{required:true,message:'请输入等级名称',trigger:['input','blur']},autoOrderDelayMaxMinutes:{validator:()=>Number(form.autoOrderDelayMaxMinutes)>=Number(form.autoOrderDelayMinMinutes),message:'最长派单时间不能小于最短派单时间',trigger:['input','blur']}};const [register]=useForm({gridProps:{cols:'1 s:1 m:2 l:3'},labelWidth:80,schemas});const loadDataTable=res=>List({...searchFormRef.value?.formModel,...res});const reloadTable=()=>actionRef.value?.reload();const reset=(o,v)=>{Object.keys(o).forEach(k=>delete o[k]);Object.assign(o,v)};
|
||||
const actionColumn=reactive({width:210,title:'操作',key:'action',fixed:'right',render:row=>h(TableAction as any,{style:'button',actions:[{label:'编辑',onClick:()=>openEdit(row),auth:['/memberLevel/view']},{label:row.status===1?'停用':'启用',onClick:()=>toggleStatus(row),auth:['/memberLevel/status']},{label:'删除',type:'error',onClick:()=>remove(row),auth:['/memberLevel/delete']}]})});const scrollX=computed(()=>adaTableScrollX(columns,actionColumn.width));
|
||||
function emptyForm(){return{id:0,levelCode:'',name:'',iconAttachmentId:0,iconUrl:'',upgradeYuan:0,giftYuan:0,dailyOrderLimit:0,commissionPercent:0,dailyWithdrawLimit:0,sort:0,status:1,remark:''}}
|
||||
async function openEdit(row?){reset(form,emptyForm());showEdit.value=true;if(!row)return;const r:any=await View({id:row.id});const v=r.published||r.draft;Object.assign(form,{id:r.level.id,levelCode:r.level.levelCode,name:r.level.name,iconAttachmentId:r.level.iconAttachmentId,iconUrl:r.level.iconUrl,sort:r.level.sort,status:r.level.status,remark:r.level.remark||'',upgradeYuan:(v?.upgradeAmount||0)/100,giftYuan:(v?.giftAmount||0)/100,dailyOrderLimit:v?.dailyOrderLimit||0,commissionPercent:(v?.commissionRate||0)/100,dailyWithdrawLimit:v?.dailyWithdrawLimit||0})}
|
||||
async function saveLevel(){try{await formRef.value?.validate()}catch{return}saving.value=true;try{await Save({...form,upgradeAmount:Math.round(form.upgradeYuan*100),giftAmount:Math.round(form.giftYuan*100),commissionRate:Math.round(form.commissionPercent*100)});message.success('保存成功');showEdit.value=false;reloadTable();dict.loadOptions(['memberLevelOption'])}finally{saving.value=false}}
|
||||
function emptyForm(){return{id:0,levelCode:'',name:'',iconAttachmentId:0,iconUrl:'',upgradeYuan:0,giftYuan:0,dailyOrderLimit:0,commissionPercent:0,dailyWithdrawLimit:0,autoOrderDelayMinMinutes:15,autoOrderDelayMaxMinutes:20,sort:0,status:1,remark:''}}
|
||||
async function openEdit(row?){reset(form,emptyForm());showEdit.value=true;if(!row)return;const r:any=await View({id:row.id});const v=r.published||r.draft;const levelRules=v?.ruleJson||{};Object.assign(form,{id:r.level.id,levelCode:r.level.levelCode,name:r.level.name,iconAttachmentId:r.level.iconAttachmentId,iconUrl:r.level.iconUrl,sort:r.level.sort,status:r.level.status,remark:r.level.remark||'',upgradeYuan:(v?.upgradeAmount||0)/100,giftYuan:(v?.giftAmount||0)/100,dailyOrderLimit:v?.dailyOrderLimit||0,commissionPercent:(v?.commissionRate||0)/100,dailyWithdrawLimit:v?.dailyWithdrawLimit||0,autoOrderDelayMinMinutes:Number(levelRules.auto_order_delay_min_seconds??900)/60,autoOrderDelayMaxMinutes:Number(levelRules.auto_order_delay_max_seconds??1200)/60})}
|
||||
async function saveLevel(){try{await formRef.value?.validate()}catch{return}saving.value=true;try{await Save({...form,upgradeAmount:Math.round(form.upgradeYuan*100),giftAmount:Math.round(form.giftYuan*100),commissionRate:Math.round(form.commissionPercent*100),autoOrderDelayMinSeconds:Math.round(form.autoOrderDelayMinMinutes*60),autoOrderDelayMaxSeconds:Math.round(form.autoOrderDelayMaxMinutes*60)});message.success('保存成功');showEdit.value=false;reloadTable();dict.loadOptions(['memberLevelOption'])}finally{saving.value=false}}
|
||||
async function toggleStatus(row){await Status({id:row.id,status:row.status===1?2:1});message.success('状态已更新');reloadTable()}
|
||||
function remove(row){dialog.warning({title:'删除等级',content:'仅无会员且未被任务大厅引用的等级可删除。',positiveText:'确认删除',negativeText:'取消',onPositiveClick:async()=>{await Delete({id:row.id,reason:'后台删除未使用等级'});message.success('删除成功');reloadTable()}})}
|
||||
onMounted(loadOptions);
|
||||
|
||||
Reference in New Issue
Block a user