feat(fsgx): HJF queue merge, brokerage timing, cycle commission, points release

- Add HJF jobs, services, DAOs, models, admin/API controllers, release command
- Respect brokerage_timing (on_pay vs confirm); dispatch HjfOrderPayJob for queue goods
- Queue-only cycle commission and position index fix in StoreOrderCreateServices
- UserBill income types: frozen_points_brokerage, frozen_points_release
- Timer: fsgx_release_frozen_points -> PointsReleaseServices
- Agent tasks: no_assess filtering for direct/umbrella counts
- Migrations: queue_pool, points_release_log, fsgx_v1 checklist updates
- Admin/uniapp: crontab preset, membership level, user list, finance routes, docs

Made-with: Cursor
This commit is contained in:
apple
2026-03-24 11:59:09 +08:00
parent 434aa8c69d
commit 76ccb24679
59 changed files with 2902 additions and 237 deletions

View File

@@ -101,6 +101,7 @@ export const defaultObj = {
is_sub: 0, //返佣方式
is_vip: 0,
level_type: 1, //默认比例 自定义
is_queue_goods: 0, //是否报单商品1=是0=否
};
export const GoodsTableHead = [

View File

@@ -239,6 +239,16 @@ export default {
minWidth: 130,
title: "二级返佣比例(上浮后)(%)",
},
{
key: "direct_reward_points",
minWidth: 120,
title: "直推奖励积分",
},
{
key: "umbrella_reward_points",
minWidth: 120,
title: "伞下奖励积分",
},
{
slot: "status",
minWidth: 80,

View File

@@ -29,6 +29,10 @@
>编辑</router-link
>
<Divider type="vertical" />
<a @click="handleRunNow(row)" :class="{ 'running': row._running }">
{{ row._running ? '执行中…' : '手动触发' }}
</a>
<Divider type="vertical" />
<a @click="handleDelete(row, '删除定时任务', index)">删除</a>
</template>
</Table>
@@ -46,7 +50,7 @@
</template>
<script>
import { timerIndex, showTimer } from '@/api/system';
import { timerIndex, showTimer, runTimerNow } from '@/api/system';
export default {
name: 'system_crontab',
@@ -79,7 +83,7 @@ export default {
slot: 'action',
align: 'center',
fixed: 'right',
minWidth: 100,
minWidth: 200,
},
],
tableData: [],
@@ -133,6 +137,21 @@ export default {
this.$Message.error(res.msg);
});
},
// 手动立即触发
handleRunNow(row) {
this.$set(row, '_running', true);
runTimerNow(row.id)
.then((res) => {
this.$Message.success(res.msg || '触发成功,任务已执行');
this.getList();
})
.catch((res) => {
this.$Message.error(res.msg || '触发失败');
})
.finally(() => {
this.$set(row, '_running', false);
});
},
// 是否开启
handleChange({ id, is_open }) {
showTimer(id, is_open)
@@ -152,4 +171,9 @@ export default {
};
</script>
<style lang="stylus" scoped></style>
<style lang="stylus" scoped>
.running
color #999
cursor not-allowed
pointer-events none
</style>

View File

@@ -54,15 +54,15 @@
<div class="iconfont iconxiayi"></div>
</div>
</FormItem>
<FormItem label="会员等级:" label-for="hjf_member_level">
<FormItem label="分销等级:" label-for="hjf_member_level">
<Select
v-model="userFrom.hjf_member_level"
placeholder="请选择"
placeholder="请选择分销等级"
element-id="hjf_member_level"
clearable
class="input-add"
>
<Option :value="0">普通会员</Option>
<Option :value="0">普通无分销等级</Option>
<Option :value="1">创客</Option>
<Option :value="2">云店</Option>
<Option :value="3">服务商</Option>
@@ -420,14 +420,15 @@
</div>
</template>
</vxe-column>
<vxe-column field="member_level" title="HJF等级" min-width="110">
<vxe-column field="member_level_name" title="分销等级" min-width="160">
<template v-slot="{ row }">
<HjfMemberBadge
v-if="row.member_level != null"
v-if="row.member_level != null && row.member_level > 0"
:level="Number(row.member_level)"
:level-name="row.member_level_name || ''"
size="small"
/>
<span v-else class="level-none">普通会员</span>
<span v-else class="level-none">普通无等级</span>
</template>
</vxe-column>
<vxe-column field="direct_count" title="直推人数" min-width="90">
@@ -452,22 +453,22 @@
title="推荐人"
min-width="100"
></vxe-column>
<vxe-column field="available_points" title="可用积分" min-width="100">
<template v-slot="{ row }">
<span>{{ row.available_points != null ? row.available_points : 0 }}</span>
</template>
</vxe-column>
<vxe-column field="frozen_points" title="待释放(冻结)积分" min-width="150">
<template v-slot="{ row }">
<span>{{ row.frozen_points != null ? row.frozen_points : 0 }}</span>
</template>
</vxe-column>
<vxe-column field="now_money" title="余额" min-width="100"></vxe-column>
<vxe-column field="frozen_points" title="待释放积分" min-width="100">
<template v-slot="{ row }">
<span>{{ row.frozen_points != null ? row.frozen_points : '-' }}</span>
</template>
</vxe-column>
<vxe-column field="available_points" title="已释放积分" min-width="100">
<template v-slot="{ row }">
<span>{{ row.available_points != null ? row.available_points : '-' }}</span>
</template>
</vxe-column>
<vxe-column
field="action"
title="操作"
align="center"
width="240"
width="180"
fixed="right"
>
<template v-slot="{ row }">
@@ -475,8 +476,6 @@
<Divider type="vertical" />
<a @click="changeMenu(row, '10')">编辑</a>
<Divider type="vertical" />
<a @click="openLevelModal(row)">调整等级</a>
<Divider type="vertical" />
<a @click="handleNoAssess(row)" :style="row.no_assess ? 'color:#19be6b' : 'color:#ed4014'">
{{ row.no_assess ? '取消不考核' : '不考核' }}
</a>
@@ -791,7 +790,7 @@ import {
exportUserData,
} from "@/api/user";
import { agentSpreadApi } from "@/api/agent";
import { memberList, memberSetLevel, memberSetNoAssess } from "@/api/hjfMember";
import { memberList, memberSetLevel, memberSetNoAssess } from "@/api/hjfMember"; // memberSetNoAssess 仍保留供 handleNoAssess 使用
import editFrom from "../../../components/from/from";
import sendFrom from "@/components/sendCoupons/index";
import userDetails from "./handle/userDetails";
@@ -881,7 +880,7 @@ export default {
group_id: "",
field_key: "",
is_channel: "",
/** 会员等级筛选(HJF 扩展字段0 普通 1 创客 2 云店 3 服务商 4 分公司,空串表示全部 */
/** 分销等级筛选(对应 eb_agent_level.grade / eb_user.agent_level,空串表示全部 */
hjf_member_level: "",
},
field_key: "",
@@ -1455,12 +1454,13 @@ export default {
this.userFrom.group_id === "all" ? "" : this.userFrom.group_id;
userList(this.userFrom)
.then(async (res) => {
let data = res.data;
data.list.forEach((item) => {
const data = res.data || {};
const rows = Array.isArray(data.list) ? data.list : [];
rows.forEach((item) => {
item.checkBox = false;
});
this.userLists = data.list;
this.total = data.count;
this.userLists = rows;
this.total = data.count != null ? data.count : 0;
this.loading = false;
this.$nextTick(function () {
if (this.isAll == 1) {