1 parent
96b213354b
commit
88db96c374
2 files changed
+709
No files matched your search
@@ -0,0 +1,93 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import request from '@/axios'
|
||||
import type { PageResult } from './types'
|
||||
|
||||
export interface RewardTaskItem {
|
||||
id: string
|
||||
code: string
|
||||
sectionCode: string
|
||||
type: string
|
||||
verifyMode: string
|
||||
targetValue: number
|
||||
rewardCoin: number
|
||||
cycleType: string
|
||||
cycleLimit: number
|
||||
actionType: string
|
||||
actionUrl: string
|
||||
iconUrl: string
|
||||
titleId: string
|
||||
titleEn: string
|
||||
titleZh: string
|
||||
subtitleId: string
|
||||
subtitleEn: string
|
||||
subtitleZh: string
|
||||
ctaId: string
|
||||
ctaEn: string
|
||||
ctaZh: string
|
||||
sectionTitleId: string
|
||||
sectionTitleEn: string
|
||||
sectionTitleZh: string
|
||||
configJson: string
|
||||
sort: number
|
||||
status: number
|
||||
statusText: string
|
||||
startAt?: string
|
||||
endAt?: string
|
||||
}
|
||||
|
||||
export interface RewardTaskPageParams {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
keyword?: string
|
||||
type?: string
|
||||
sectionCode?: string
|
||||
status?: number
|
||||
}
|
||||
|
||||
export type RewardTaskSaveData = Omit<RewardTaskItem, 'id' | 'status' | 'statusText'>
|
||||
|
||||
export interface RewardRuleDay {
|
||||
dayIndex: number
|
||||
rewardCoin: number
|
||||
}
|
||||
|
||||
export interface RewardGrantItem {
|
||||
id: string
|
||||
userId: string
|
||||
taskId: string
|
||||
cycleKey: string
|
||||
amount: number
|
||||
status: string
|
||||
idempotentKey: string
|
||||
coinLedgerId: string
|
||||
source: string
|
||||
operatorId: string
|
||||
failureReason: string
|
||||
grantedAt?: string
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
export const pageRewardTasksApi = (params: RewardTaskPageParams) =>
|
||||
request.get<PageResult<RewardTaskItem>>({ url: '/admin/rewards/tasks/page', params })
|
||||
|
||||
export const createRewardTaskApi = (data: RewardTaskSaveData) =>
|
||||
request.post<string>({ url: '/admin/rewards/tasks', data })
|
||||
|
||||
export const updateRewardTaskApi = (id: string, data: RewardTaskSaveData) =>
|
||||
request.put<void>({ url: `/admin/rewards/tasks/${id}`, data })
|
||||
|
||||
export const updateRewardTaskStatusApi = (id: string, status: number) =>
|
||||
request.put<void>({ url: `/admin/rewards/tasks/${id}/status`, params: { status } })
|
||||
|
||||
export const getRewardRulesApi = () =>
|
||||
request.get<RewardRuleDay[]>({ url: '/admin/rewards/check-in/rules' })
|
||||
|
||||
export const saveRewardRulesApi = (days: RewardRuleDay[]) =>
|
||||
request.put<void>({ url: '/admin/rewards/check-in/rules', data: { days } })
|
||||
|
||||
export const pageRewardGrantsApi = (params: Record<string, unknown>) =>
|
||||
request.get<PageResult<RewardGrantItem>>({ url: '/admin/rewards/grants/page', params })
|
||||
|
||||
export const manualRewardGrantApi = (data: Record<string, unknown>) =>
|
||||
request.post<RewardGrantItem>({ url: '/admin/rewards/grants/manual', data })
|
||||
@@ -0,0 +1,616 @@
|
||||
<!-- Author: fruit.com.cn -->
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PageHeading from '@/views/components/PageHeading.vue'
|
||||
import { useRbac } from '@/stores/rbac'
|
||||
import { dateCell, formatCount } from '@/utils/format'
|
||||
import {
|
||||
createRewardTaskApi,
|
||||
getRewardRulesApi,
|
||||
manualRewardGrantApi,
|
||||
pageRewardGrantsApi,
|
||||
pageRewardTasksApi,
|
||||
saveRewardRulesApi,
|
||||
updateRewardTaskApi,
|
||||
updateRewardTaskStatusApi
|
||||
} from '@/api/admin/reward'
|
||||
|
||||
const { can } = useRbac()
|
||||
const activeTab = ref('tasks')
|
||||
const typeOptions = [
|
||||
['DAILY_CHECK_IN', '每日签到'],
|
||||
['WATCH_DURATION', '观看时长'],
|
||||
['WATCH_EPISODES', '观看集数'],
|
||||
['FIRST_LOGIN', '首次登录'],
|
||||
['COMPLETE_PROFILE', '完善资料'],
|
||||
['FIRST_PURCHASE', '首次购买'],
|
||||
['FOLLOW_SOCIAL', '关注社交账号'],
|
||||
['INVITE_FRIEND', '邀请好友'],
|
||||
['CUSTOM_LINK', '自定义任务']
|
||||
]
|
||||
const cycleOptions = [
|
||||
['ONCE', '一次性'],
|
||||
['DAILY', '每日'],
|
||||
['WEEKLY', '每周']
|
||||
]
|
||||
const sectionOptions = [
|
||||
['CHECK_IN', '签到奖励'],
|
||||
['DAILY', '每日任务'],
|
||||
['NEWBIE', '新手任务'],
|
||||
['ACTIVITY', '活动任务']
|
||||
]
|
||||
const actionOptions = [
|
||||
['NONE', '无动作'],
|
||||
['OPEN_HOME', '打开首页'],
|
||||
['OPEN_PROFILE', '打开个人资料'],
|
||||
['OPEN_WALLET', '打开钱包'],
|
||||
['OPEN_URL', '打开链接']
|
||||
]
|
||||
|
||||
const tasks = reactive({ rows: [], total: 0, page: 1, size: 10, loading: false })
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
type: undefined,
|
||||
sectionCode: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const editingId = ref(null)
|
||||
const emptyForm = () => ({
|
||||
code: '',
|
||||
sectionCode: 'DAILY',
|
||||
type: 'WATCH_DURATION',
|
||||
verifyMode: 'SERVER',
|
||||
targetValue: 1,
|
||||
rewardCoin: 10,
|
||||
cycleType: 'DAILY',
|
||||
cycleLimit: 1,
|
||||
actionType: 'OPEN_HOME',
|
||||
actionUrl: '',
|
||||
iconUrl: '',
|
||||
titleId: '',
|
||||
titleEn: '',
|
||||
titleZh: '',
|
||||
subtitleId: '',
|
||||
subtitleEn: '',
|
||||
subtitleZh: '',
|
||||
ctaId: 'Mulai',
|
||||
ctaEn: 'Start',
|
||||
ctaZh: '去完成',
|
||||
sectionTitleId: 'Tugas harian',
|
||||
sectionTitleEn: 'Daily tasks',
|
||||
sectionTitleZh: '每日任务',
|
||||
configJson: '',
|
||||
sort: 0,
|
||||
startAt: null,
|
||||
endAt: null
|
||||
})
|
||||
const form = reactive(emptyForm())
|
||||
|
||||
async function loadTasks() {
|
||||
tasks.loading = true
|
||||
try {
|
||||
const res = await pageRewardTasksApi({
|
||||
pageIndex: tasks.page,
|
||||
pageSize: tasks.size,
|
||||
keyword: filters.keyword.trim() || undefined,
|
||||
type: filters.type,
|
||||
sectionCode: filters.sectionCode,
|
||||
status: filters.status
|
||||
})
|
||||
tasks.rows = res.data?.list || []
|
||||
tasks.total = res.data?.total || 0
|
||||
} finally {
|
||||
tasks.loading = false
|
||||
}
|
||||
}
|
||||
function searchTasks() {
|
||||
tasks.page = 1
|
||||
loadTasks()
|
||||
}
|
||||
function resetFilters() {
|
||||
Object.assign(filters, {
|
||||
keyword: '',
|
||||
type: undefined,
|
||||
sectionCode: undefined,
|
||||
status: undefined
|
||||
})
|
||||
searchTasks()
|
||||
}
|
||||
function openCreate() {
|
||||
editingId.value = null
|
||||
Object.assign(form, emptyForm(), { sort: tasks.total + 1 })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function openEdit(row) {
|
||||
editingId.value = row.id
|
||||
Object.assign(form, emptyForm(), row)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
function validateTask() {
|
||||
if (!form.code.trim() || !form.titleId.trim() || !form.sectionTitleId.trim())
|
||||
return '请填写编码、印尼语标题和分组标题'
|
||||
if (form.targetValue <= 0 || form.rewardCoin < 0) return '目标值必须大于 0,奖励金币不能小于 0'
|
||||
return ''
|
||||
}
|
||||
async function saveTask() {
|
||||
const invalid = validateTask()
|
||||
if (invalid) return ElMessage.warning(invalid)
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
...form,
|
||||
code: form.code.trim(),
|
||||
titleId: form.titleId.trim(),
|
||||
sectionTitleId: form.sectionTitleId.trim()
|
||||
}
|
||||
if (editingId.value) await updateRewardTaskApi(editingId.value, payload)
|
||||
else await createRewardTaskApi(payload)
|
||||
ElMessage.success(editingId.value ? '任务已更新' : '任务已创建,默认下线')
|
||||
dialogVisible.value = false
|
||||
await loadTasks()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
async function toggleTask(row) {
|
||||
await updateRewardTaskStatusApi(row.id, row.status === 1 ? 0 : 1)
|
||||
await loadTasks()
|
||||
}
|
||||
|
||||
const rules = ref([])
|
||||
const rulesLoading = ref(false)
|
||||
const rulesSaving = ref(false)
|
||||
async function loadRules() {
|
||||
rulesLoading.value = true
|
||||
try {
|
||||
rules.value = (await getRewardRulesApi()).data || []
|
||||
} finally {
|
||||
rulesLoading.value = false
|
||||
}
|
||||
}
|
||||
async function saveRules() {
|
||||
rulesSaving.value = true
|
||||
try {
|
||||
await saveRewardRulesApi(
|
||||
rules.value.map(({ dayIndex, rewardCoin }) => ({ dayIndex, rewardCoin }))
|
||||
)
|
||||
ElMessage.success('签到规则已保存')
|
||||
} finally {
|
||||
rulesSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const grants = reactive({
|
||||
rows: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
size: 10,
|
||||
loading: false,
|
||||
userId: '',
|
||||
status: undefined
|
||||
})
|
||||
const grantVisible = ref(false)
|
||||
const grantSaving = ref(false)
|
||||
const grantForm = reactive({ userId: '', taskId: '', amount: 10, idempotentKey: '', reason: '' })
|
||||
async function loadGrants() {
|
||||
grants.loading = true
|
||||
try {
|
||||
const res = await pageRewardGrantsApi({
|
||||
pageIndex: grants.page,
|
||||
pageSize: grants.size,
|
||||
userId: grants.userId || undefined,
|
||||
status: grants.status
|
||||
})
|
||||
grants.rows = res.data?.list || []
|
||||
grants.total = res.data?.total || 0
|
||||
} finally {
|
||||
grants.loading = false
|
||||
}
|
||||
}
|
||||
function queryGrants() {
|
||||
grants.page = 1
|
||||
loadGrants()
|
||||
}
|
||||
function openGrant() {
|
||||
Object.assign(grantForm, {
|
||||
userId: grants.userId,
|
||||
taskId: '',
|
||||
amount: 10,
|
||||
idempotentKey: '',
|
||||
reason: ''
|
||||
})
|
||||
grantVisible.value = true
|
||||
}
|
||||
async function confirmGrant() {
|
||||
if (!grantForm.userId || grantForm.amount <= 0)
|
||||
return ElMessage.warning('请填写用户 ID 和补发金币')
|
||||
grantSaving.value = true
|
||||
try {
|
||||
await manualRewardGrantApi({
|
||||
...grantForm,
|
||||
taskId: grantForm.taskId || undefined,
|
||||
idempotentKey: grantForm.idempotentKey || undefined
|
||||
})
|
||||
ElMessage.success('奖励已通过钱包流水补发')
|
||||
grantVisible.value = false
|
||||
await loadGrants()
|
||||
} finally {
|
||||
grantSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleTab(name) {
|
||||
if (name === 'rules' && !rules.value.length) loadRules()
|
||||
if (name === 'grants' && !grants.rows.length) loadGrants()
|
||||
}
|
||||
function optionLabel(options, value) {
|
||||
return options.find((item) => item[0] === value)?.[1] || value || '-'
|
||||
}
|
||||
onMounted(loadTasks)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageHeading
|
||||
eyebrow="订单与权益"
|
||||
title="奖励任务"
|
||||
description="配置奖励页任务、签到规则和发放记录,所有金币变动均写钱包流水。"
|
||||
>
|
||||
<el-button
|
||||
v-if="activeTab === 'tasks' && can('reward:update')"
|
||||
type="primary"
|
||||
@click="openCreate"
|
||||
>新建任务</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="activeTab === 'grants' && can('reward:grant')"
|
||||
type="primary"
|
||||
@click="openGrant"
|
||||
>人工补发</el-button
|
||||
>
|
||||
</PageHeading>
|
||||
|
||||
<section class="panel reward-panel">
|
||||
<el-tabs v-model="activeTab" @tab-change="handleTab">
|
||||
<el-tab-pane label="任务配置" name="tasks">
|
||||
<div class="reward-toolbar">
|
||||
<el-input
|
||||
v-model="filters.keyword"
|
||||
clearable
|
||||
placeholder="任务编码 / 标题"
|
||||
@keyup.enter="searchTasks"
|
||||
/>
|
||||
<el-select v-model="filters.type" clearable placeholder="全部类型" @change="searchTasks"
|
||||
><el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]"
|
||||
/></el-select>
|
||||
<el-select
|
||||
v-model="filters.sectionCode"
|
||||
clearable
|
||||
placeholder="全部分组"
|
||||
@change="searchTasks"
|
||||
><el-option
|
||||
v-for="item in sectionOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]"
|
||||
/></el-select>
|
||||
<el-select v-model="filters.status" clearable placeholder="全部状态" @change="searchTasks"
|
||||
><el-option label="已上线" :value="1" /><el-option label="已下线" :value="0"
|
||||
/></el-select>
|
||||
<el-button @click="searchTasks">查询</el-button
|
||||
><el-button @click="resetFilters">重置</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="tasks.loading"
|
||||
:data="tasks.rows"
|
||||
row-key="id"
|
||||
empty-text="暂无奖励任务"
|
||||
>
|
||||
<el-table-column prop="code" label="任务编码" min-width="150" />
|
||||
<el-table-column label="标题" min-width="180"
|
||||
><template #default="{ row }"
|
||||
><strong>{{ row.titleId }}</strong
|
||||
><div class="sub-copy">{{ row.titleZh || row.titleEn }}</div></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="类型" width="130"
|
||||
><template #default="{ row }">{{
|
||||
optionLabel(typeOptions, row.type)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="分组" width="110"
|
||||
><template #default="{ row }">{{
|
||||
optionLabel(sectionOptions, row.sectionCode)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="目标 / 奖励" width="130"
|
||||
><template #default="{ row }"
|
||||
>{{ formatCount(row.targetValue) }} / +{{ formatCount(row.rewardCoin) }}</template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="周期" width="100"
|
||||
><template #default="{ row }">{{
|
||||
optionLabel(cycleOptions, row.cycleType)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="状态" width="90"
|
||||
><template #default="{ row }"
|
||||
><el-tag :type="row.status === 1 ? 'success' : 'info'">{{
|
||||
row.statusText
|
||||
}}</el-tag></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="操作" width="170" fixed="right"
|
||||
><template #default="{ row }"
|
||||
><el-button v-if="can('reward:update')" link type="primary" @click="openEdit(row)"
|
||||
>编辑</el-button
|
||||
><el-button
|
||||
v-if="can('reward:update')"
|
||||
link
|
||||
:type="row.status === 1 ? 'danger' : 'success'"
|
||||
@click="toggleTask(row)"
|
||||
>{{ row.status === 1 ? '下线' : '上线' }}</el-button
|
||||
></template
|
||||
></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="tasks.page"
|
||||
v-model:page-size="tasks.size"
|
||||
:total="tasks.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTasks"
|
||||
@size-change="searchTasks"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="签到规则" name="rules">
|
||||
<div v-loading="rulesLoading" class="rule-grid"
|
||||
><div v-for="day in rules" :key="day.dayIndex" class="rule-card"
|
||||
><span>第 {{ day.dayIndex }} 天</span
|
||||
><el-input-number v-model="day.rewardCoin" :min="0" /><small>金币</small></div
|
||||
></div
|
||||
>
|
||||
<el-button
|
||||
v-if="can('reward:checkin')"
|
||||
type="primary"
|
||||
:loading="rulesSaving"
|
||||
@click="saveRules"
|
||||
>保存签到规则</el-button
|
||||
>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="发放记录" name="grants">
|
||||
<div class="reward-toolbar"
|
||||
><el-input
|
||||
v-model="grants.userId"
|
||||
clearable
|
||||
placeholder="用户 ID"
|
||||
@keyup.enter="queryGrants"
|
||||
/><el-select
|
||||
v-model="grants.status"
|
||||
clearable
|
||||
placeholder="全部状态"
|
||||
@change="queryGrants"
|
||||
><el-option label="已发放" value="GRANTED" /><el-option
|
||||
label="待处理"
|
||||
value="PENDING" /><el-option label="失败" value="FAILED" /></el-select
|
||||
><el-button @click="queryGrants">查询</el-button></div
|
||||
>
|
||||
<el-table
|
||||
v-loading="grants.loading"
|
||||
:data="grants.rows"
|
||||
row-key="id"
|
||||
empty-text="暂无发放记录"
|
||||
>
|
||||
<el-table-column prop="userId" label="用户 ID" min-width="160" /><el-table-column
|
||||
prop="taskId"
|
||||
label="任务 ID"
|
||||
min-width="160"
|
||||
/><el-table-column label="金币" width="100"
|
||||
><template #default="{ row }">+{{ formatCount(row.amount) }}</template></el-table-column
|
||||
><el-table-column prop="source" label="来源" width="90" /><el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
width="100"
|
||||
/><el-table-column
|
||||
prop="idempotentKey"
|
||||
label="幂等键"
|
||||
min-width="240"
|
||||
show-overflow-tooltip
|
||||
/><el-table-column label="发放时间" width="180"
|
||||
><template #default="{ row }">{{
|
||||
dateCell(row.grantedAt || row.createdAt)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="grants.page"
|
||||
v-model:page-size="grants.size"
|
||||
:total="grants.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadGrants"
|
||||
@size-change="queryGrants"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="editingId ? '编辑奖励任务' : '新建奖励任务'"
|
||||
width="780px"
|
||||
>
|
||||
<el-form label-position="top" class="task-form"
|
||||
><el-row :gutter="16"
|
||||
><el-col :span="8"
|
||||
><el-form-item label="任务编码"
|
||||
><el-input v-model="form.code" :disabled="Boolean(editingId)" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="任务类型"
|
||||
><el-select v-model="form.type"
|
||||
><el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]" /></el-select></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="分组"
|
||||
><el-select v-model="form.sectionCode"
|
||||
><el-option
|
||||
v-for="item in sectionOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]" /></el-select></el-form-item></el-col
|
||||
></el-row>
|
||||
<el-row :gutter="16"
|
||||
><el-col :span="6"
|
||||
><el-form-item label="目标值"
|
||||
><el-input-number v-model="form.targetValue" :min="1" /></el-form-item></el-col
|
||||
><el-col :span="6"
|
||||
><el-form-item label="奖励金币"
|
||||
><el-input-number v-model="form.rewardCoin" :min="0" /></el-form-item></el-col
|
||||
><el-col :span="6"
|
||||
><el-form-item label="周期"
|
||||
><el-select v-model="form.cycleType"
|
||||
><el-option
|
||||
v-for="item in cycleOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]" /></el-select></el-form-item></el-col
|
||||
><el-col :span="6"
|
||||
><el-form-item label="排序"
|
||||
><el-input-number v-model="form.sort" :min="0" /></el-form-item></el-col
|
||||
></el-row>
|
||||
<el-divider content-position="left">多语言文案</el-divider
|
||||
><el-row :gutter="16"
|
||||
><el-col :span="8"
|
||||
><el-form-item label="印尼语标题"
|
||||
><el-input v-model="form.titleId" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="英文标题"><el-input v-model="form.titleEn" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="中文标题"><el-input v-model="form.titleZh" /></el-form-item></el-col
|
||||
></el-row>
|
||||
<el-row :gutter="16"
|
||||
><el-col :span="8"
|
||||
><el-form-item label="印尼语说明"
|
||||
><el-input v-model="form.subtitleId" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="英文说明"
|
||||
><el-input v-model="form.subtitleEn" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="中文说明"
|
||||
><el-input v-model="form.subtitleZh" /></el-form-item></el-col
|
||||
></el-row>
|
||||
<el-row :gutter="16"
|
||||
><el-col :span="8"
|
||||
><el-form-item label="印尼语分组标题"
|
||||
><el-input v-model="form.sectionTitleId" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="英文分组标题"
|
||||
><el-input v-model="form.sectionTitleEn" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="中文分组标题"
|
||||
><el-input v-model="form.sectionTitleZh" /></el-form-item></el-col
|
||||
></el-row>
|
||||
<el-divider content-position="left">动作与扩展</el-divider
|
||||
><el-row :gutter="16"
|
||||
><el-col :span="8"
|
||||
><el-form-item label="完成按钮"><el-input v-model="form.ctaId" /></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="动作"
|
||||
><el-select v-model="form.actionType"
|
||||
><el-option
|
||||
v-for="item in actionOptions"
|
||||
:key="item[0]"
|
||||
:label="item[1]"
|
||||
:value="item[0]" /></el-select></el-form-item></el-col
|
||||
><el-col :span="8"
|
||||
><el-form-item label="动作链接"
|
||||
><el-input v-model="form.actionUrl" /></el-form-item></el-col></el-row
|
||||
><el-form-item label="扩展配置 JSON"
|
||||
><el-input v-model="form.configJson" type="textarea" :rows="3"
|
||||
/></el-form-item>
|
||||
</el-form>
|
||||
<template #footer
|
||||
><el-button @click="dialogVisible = false">取消</el-button
|
||||
><el-button type="primary" :loading="saving" @click="saveTask">保存</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="grantVisible" title="人工补发奖励" width="520px"
|
||||
><el-form label-position="top"
|
||||
><el-form-item label="用户 ID"><el-input v-model="grantForm.userId" /></el-form-item
|
||||
><el-form-item label="任务 ID(可选)"><el-input v-model="grantForm.taskId" /></el-form-item
|
||||
><el-form-item label="补发金币"
|
||||
><el-input-number v-model="grantForm.amount" :min="1" /></el-form-item
|
||||
><el-form-item label="幂等键(重复补发时必须相同)"
|
||||
><el-input v-model="grantForm.idempotentKey" /></el-form-item
|
||||
><el-form-item label="原因"><el-input v-model="grantForm.reason" /></el-form-item></el-form
|
||||
><template #footer
|
||||
><el-button @click="grantVisible = false">取消</el-button
|
||||
><el-button type="primary" :loading="grantSaving" @click="confirmGrant"
|
||||
>确认补发</el-button
|
||||
></template
|
||||
></el-dialog
|
||||
>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reward-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
.reward-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.reward-toolbar .el-input,
|
||||
.reward-toolbar .el-select {
|
||||
width: 180px;
|
||||
}
|
||||
.sub-copy {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.rule-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(100px, 1fr));
|
||||
gap: 12px;
|
||||
margin: 8px 0 20px;
|
||||
}
|
||||
.rule-card {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 12px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
.rule-card small {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.task-form :deep(.el-select),
|
||||
.task-form :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
.el-pagination {
|
||||
justify-content: flex-end;
|
||||
margin-top: 18px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.rule-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user