feat: add syj promote workflow

This commit is contained in:
apple
2026-05-03 14:44:12 +08:00
parent 12c2431d4e
commit 0e07a65e3f
36 changed files with 1972 additions and 1 deletions

View File

@@ -0,0 +1,37 @@
import request from '@/plugins/request';
export function taskListApi(params) {
return request({ url: 'syj/promote/task/list', method: 'get', params });
}
export function taskDetailApi(id) {
return request({ url: `syj/promote/task/${id}`, method: 'get' });
}
export function taskRecordsApi(id) {
return request({ url: `syj/promote/task/${id}/records`, method: 'get' });
}
export function cashoutListApi(params) {
return request({ url: 'syj/promote/cashout/list', method: 'get', params });
}
export function auditCashoutApi(id, data) {
return request({ url: `syj/promote/cashout/${id}/audit`, method: 'post', data });
}
export function settlementListApi(params) {
return request({ url: 'syj/promote/settlement/list', method: 'get', params });
}
export function configGetApi() {
return request({ url: 'syj/promote/config', method: 'get' });
}
export function configSaveApi(data) {
return request({ url: 'syj/promote/config', method: 'post', data });
}
export function retryTriggerApi(taskId) {
return request({ url: `syj/promote/retry-trigger/${taskId}`, method: 'post' });
}

View File

@@ -0,0 +1,68 @@
<template>
<div>
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form inline @submit.native.prevent>
<FormItem label="关键词:"><Input v-model="query.keyword" placeholder="任务号/用户" class="input-add" clearable /></FormItem>
<FormItem label="状态:">
<Select v-model="query.audit_status" class="input-add" clearable>
<Option :value="0">待审核</Option>
<Option :value="1">已通过</Option>
<Option :value="2">已拒绝</Option>
</Select>
</FormItem>
<FormItem><Button type="primary" @click="getList">查询</Button></FormItem>
</Form>
</div>
</Card>
<Card :bordered="false" dis-hover class="ivu-mt">
<Table :columns="columns" :data="list" :loading="loading">
<template slot-scope="{ row }" slot="amount">¥{{ row.net_amount }} / 应结 ¥{{ row.gross_amount }}</template>
<template slot-scope="{ row }" slot="status">{{ ['待审核','已通过','已拒绝'][row.audit_status] }}</template>
<template slot-scope="{ row }" slot="action">
<Button v-if="row.audit_status == 0" size="small" type="primary" @click="audit(row, 1)">通过</Button>
<Button v-if="row.audit_status == 0" size="small" class="ivu-ml-8" @click="audit(row, 2)">拒绝</Button>
</template>
</Table>
</Card>
</div>
</template>
<script>
import { cashoutListApi, auditCashoutApi } from '@/api/syjPromote.js';
export default {
name: 'SyjCashout',
data() {
return {
loading: false,
list: [],
query: { keyword: '', audit_status: 0, page: 1, limit: 20 },
columns: [
{ title: '任务号', key: 'task_no', minWidth: 180 },
{ title: '用户', key: 'nickname', minWidth: 140 },
{ title: '到账/应结', slot: 'amount', minWidth: 160 },
{ title: '扣费', key: 'fee_amount', width: 100 },
{ title: '状态', slot: 'status', width: 100 },
{ title: '操作', slot: 'action', width: 160 }
]
};
},
created() { this.getList(); },
methods: {
getList() {
this.loading = true;
cashoutListApi(this.query).then(res => {
const data = res.data || res;
this.list = data.list || [];
}).finally(() => { this.loading = false; });
},
audit(row, status) {
auditCashoutApi(row.id, { status, remark: status === 1 ? '审核通过' : '审核拒绝' }).then(() => {
this.$Message.success('审核成功');
this.getList();
});
}
}
};
</script>

View File

@@ -0,0 +1,63 @@
<template>
<Card :bordered="false" dis-hover class="ivu-mt">
<p slot="title">芍药居任务配置</p>
<Spin v-if="loading" fix />
<Form v-else :model="form" :label-width="180" @submit.native.prevent>
<FormItem label="任务基准金额"><InputNumber v-model="form.base_amount" :min="1" style="width:180px" /></FormItem>
<FormItem label="目标单数"><InputNumber v-model="form.target_count" :min="1" :precision="0" style="width:180px" /></FormItem>
<FormItem label="奖励比例"><Input v-model="ratesText" placeholder="10,20,30,40" style="width:260px" /></FormItem>
<FormItem label="提前兑现扣费比例"><InputNumber v-model="form.early_cashout_fee_rate" :min="0" :max="100" style="width:180px" /></FormItem>
<FormItem label="任务生成节点">
<RadioGroup v-model="form.task_generate_timing">
<Radio label="on_confirm">确认收货</Radio>
<Radio label="on_pay">支付成功</Radio>
</RadioGroup>
</FormItem>
<FormItem label="奖励触发"><i-switch v-model="form.reward_trigger_enable" :true-value="1" :false-value="0" /></FormItem>
<FormItem><Button type="primary" :loading="saving" @click="save">保存</Button></FormItem>
</Form>
</Card>
</template>
<script>
import { configGetApi, configSaveApi } from '@/api/syjPromote.js';
export default {
name: 'SyjConfig',
data() {
return {
loading: false,
saving: false,
ratesText: '10,20,30,40',
form: {
base_amount: 4333,
target_count: 4,
reward_rates: [10, 20, 30, 40],
early_cashout_fee_rate: 7,
task_generate_timing: 'on_confirm',
task_order_dedupe: 'order',
reward_trigger_enable: 1
}
};
},
created() { this.load(); },
methods: {
load() {
this.loading = true;
configGetApi().then(res => {
this.form = Object.assign(this.form, res.data || res);
this.ratesText = (this.form.reward_rates || []).join(',');
}).finally(() => { this.loading = false; });
},
save() {
this.saving = true;
const data = Object.assign({}, this.form, {
reward_rates: this.ratesText.split(',').map(item => Number(item.trim())).filter(item => !Number.isNaN(item))
});
configSaveApi(data).then(() => {
this.$Message.success('保存成功');
}).finally(() => { this.saving = false; });
}
}
};
</script>

View File

@@ -0,0 +1,109 @@
<template>
<div>
<Card :bordered="false" dis-hover class="ivu-mt" :padding="0">
<div class="new_card_pd">
<Form inline @submit.native.prevent>
<FormItem label="关键词:">
<Input v-model="query.keyword" placeholder="任务号/订单号/用户" class="input-add" clearable />
</FormItem>
<FormItem label="状态:">
<Select v-model="query.status" class="input-add" clearable>
<Option :value="0">进行中</Option>
<Option :value="1">已完成</Option>
<Option :value="2">提前兑现</Option>
<Option :value="4">异常</Option>
<Option :value="5">审核中</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" class="mr14" @click="getList">查询</Button>
<Button @click="reset">重置</Button>
</FormItem>
</Form>
</div>
</Card>
<Card :bordered="false" dis-hover class="ivu-mt">
<Table :columns="columns" :data="list" :loading="loading">
<template slot-scope="{ row }" slot="status">{{ statusText(row.status) }}</template>
<template slot-scope="{ row }" slot="progress">{{ row.progress_count }}/{{ row.target_count }}</template>
<template slot-scope="{ row }" slot="amount">¥{{ Number(row.base_amount).toFixed(2) }}</template>
<template slot-scope="{ row }" slot="action">
<Button size="small" type="primary" @click="showDetail(row)">详情</Button>
<Button v-if="row.reward_trigger_status == 2" size="small" class="ivu-ml-8" @click="retry(row)">重试奖励</Button>
</template>
</Table>
<Page class="mt20" :total="total" :current="query.page" :page-size="query.limit" show-total @on-change="p => { query.page = p; getList(); }" />
</Card>
<Modal v-model="detailVisible" width="720" title="推广任务详情">
<pre class="detail-json">{{ detail }}</pre>
</Modal>
</div>
</template>
<script>
import { taskListApi, taskDetailApi, retryTriggerApi } from '@/api/syjPromote.js';
export default {
name: 'SyjPromoteTask',
data() {
return {
loading: false,
detailVisible: false,
detail: '',
total: 0,
list: [],
query: { keyword: '', status: '', page: 1, limit: 20 },
columns: [
{ title: '任务号', key: 'task_no', minWidth: 180 },
{ title: '用户', key: 'nickname', minWidth: 140 },
{ title: '来源订单', key: 'source_order_no', minWidth: 160 },
{ title: '金额', slot: 'amount', width: 110 },
{ title: '进度', slot: 'progress', width: 90 },
{ title: '状态', slot: 'status', width: 100 },
{ title: '创建时间', key: 'add_time', width: 140 },
{ title: '操作', slot: 'action', width: 180, fixed: 'right' }
]
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
taskListApi(this.query).then(res => {
const data = res.data || res;
this.list = data.list || [];
this.total = data.count || 0;
}).finally(() => { this.loading = false; });
},
reset() {
this.query = { keyword: '', status: '', page: 1, limit: 20 };
this.getList();
},
showDetail(row) {
taskDetailApi(row.id).then(res => {
this.detail = JSON.stringify(res.data || res, null, 2);
this.detailVisible = true;
});
},
retry(row) {
retryTriggerApi(row.id).then(() => {
this.$Message.success('重试完成');
this.getList();
});
},
statusText(status) {
return ['进行中', '已完成', '提前兑现', '已关闭', '异常', '审核中'][Number(status)] || '未知';
}
}
};
</script>
<style scoped>
.detail-json {
max-height: 520px;
overflow: auto;
white-space: pre-wrap;
}
</style>

View File

@@ -0,0 +1,34 @@
import BasicLayout from '@/layouts/basic-layout';
const pre = 'syj_';
export default {
path: '/admin/syj',
name: 'syj',
header: 'syj',
meta: {
auth: ['admin-syj'],
title: '芍药居'
},
component: BasicLayout,
children: [
{
path: 'promote/task',
name: `${pre}promoteTask`,
meta: { auth: ['syj-promote-task'], title: '推广任务' },
component: () => import('@/pages/syj/promoteTask/index')
},
{
path: 'promote/cashout',
name: `${pre}cashout`,
meta: { auth: ['syj-promote-cashout'], title: '提前兑现审核' },
component: () => import('@/pages/syj/cashout/index')
},
{
path: 'promote/config',
name: `${pre}config`,
meta: { auth: ['syj-promote-config'], title: '任务配置' },
component: () => import('@/pages/syj/config/index')
}
]
};

View File

@@ -26,6 +26,7 @@ import work from "./modules/work";
import content from "./modules/content";
import inventory from "./modules/inventory";
import hjfQueue from "./modules/hjfCustom.js";
import syj from "./modules/syj.js";
import { isSupplierPath } from "@/utils/pathUtils";
/**
@@ -210,7 +211,8 @@ const frameIn = [
work,
content,
inventory,
hjfQueue
hjfQueue,
syj
];
/**