增加积分调整日志
This commit is contained in:
71
app/admin/controller/mall/PointsLog.php
Normal file
71
app/admin/controller/mall/PointsLog.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller\mall;
|
||||
|
||||
use app\admin\model\MallPointsLog;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\model\MallUserAsset;
|
||||
use support\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class PointsLog extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array $withJoinTable = ['userAsset', 'admin'];
|
||||
protected array|string $preExcludeFields = ['id', 'before', 'after', 'memo', 'admin_id', 'create_time'];
|
||||
protected array|string $quickSearchField = [
|
||||
'userAsset.playx_user_id',
|
||||
'userAsset.username',
|
||||
'userAsset.phone',
|
||||
];
|
||||
protected bool $autoFillAdminId = true;
|
||||
|
||||
protected function initController(Request $request): ?Response
|
||||
{
|
||||
$this->model = new MallPointsLog();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function add(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($request->method() === 'POST') {
|
||||
return $this->_add();
|
||||
}
|
||||
|
||||
$userAssetId = $request->get('userAssetId', $request->post('userAssetId', 0));
|
||||
$userAsset = MallUserAsset::where('id', $userAssetId)->find();
|
||||
if (!$userAsset) {
|
||||
return $this->error(__('User asset not found'));
|
||||
}
|
||||
|
||||
return $this->success('', ['userAsset' => $userAsset]);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->error(__('Points log cannot be modified'));
|
||||
}
|
||||
|
||||
public function del(Request $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->error(__('Points log cannot be deleted'));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,14 @@ class UserAsset extends Backend
|
||||
*/
|
||||
protected ?object $model = null;
|
||||
|
||||
protected array|string $preExcludeFields = ['id', 'create_time', 'update_time'];
|
||||
protected array|string $preExcludeFields = [
|
||||
'id',
|
||||
'locked_points',
|
||||
'available_points',
|
||||
'today_claimed',
|
||||
'create_time',
|
||||
'update_time',
|
||||
];
|
||||
|
||||
protected string|array $quickSearchField = [
|
||||
'playx_user_id',
|
||||
|
||||
13
app/admin/lang/en/mall/pointslog.php
Normal file
13
app/admin/lang/en/mall/pointslog.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'User asset' => 'User asset',
|
||||
'Points adjustment' => 'Points adjustment',
|
||||
'User asset not found' => 'User asset not found',
|
||||
'Points adjustment must be a non-zero integer' => 'Points adjustment must be a non-zero integer',
|
||||
'Insufficient available points' => 'Insufficient available points',
|
||||
'Administrator added %s points' => 'Administrator added %s points',
|
||||
'Administrator deducted %s points' => 'Administrator deducted %s points',
|
||||
'Points log cannot be modified' => 'Points log cannot be modified',
|
||||
'Points log cannot be deleted' => 'Points log cannot be deleted',
|
||||
];
|
||||
13
app/admin/lang/zh-cn/mall/pointslog.php
Normal file
13
app/admin/lang/zh-cn/mall/pointslog.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'User asset' => '用户资产',
|
||||
'Points adjustment' => '调整积分',
|
||||
'User asset not found' => '用户资产不存在',
|
||||
'Points adjustment must be a non-zero integer' => '调整积分必须为非零整数',
|
||||
'Insufficient available points' => '可用积分不足',
|
||||
'Administrator added %s points' => '管理员增加积分 %s',
|
||||
'Administrator deducted %s points' => '管理员扣除积分 %s',
|
||||
'Points log cannot be modified' => '积分流水不允许修改',
|
||||
'Points log cannot be deleted' => '积分流水不允许删除',
|
||||
];
|
||||
61
app/admin/model/MallPointsLog.php
Normal file
61
app/admin/model/MallPointsLog.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use app\common\model\MallUserAsset;
|
||||
use think\model\relation\BelongsTo;
|
||||
|
||||
class MallPointsLog extends \app\common\model\MallPointsLog
|
||||
{
|
||||
public static function onBeforeInsert($model): void
|
||||
{
|
||||
$userAssetId = filter_var($model->user_asset_id, FILTER_VALIDATE_INT);
|
||||
if ($userAssetId === false || $userAssetId <= 0) {
|
||||
throw new \InvalidArgumentException(__('User asset not found'));
|
||||
}
|
||||
|
||||
$scoreValue = filter_var($model->score_value, FILTER_VALIDATE_INT);
|
||||
if ($scoreValue === false || $scoreValue === 0) {
|
||||
throw new \InvalidArgumentException(__('Points adjustment must be a non-zero integer'));
|
||||
}
|
||||
|
||||
$userAsset = MallUserAsset::where('id', $userAssetId)->lock(true)->find();
|
||||
if (!$userAsset) {
|
||||
throw new \RuntimeException(__('User asset not found'));
|
||||
}
|
||||
|
||||
$before = $userAsset->available_points;
|
||||
$after = $before + $scoreValue;
|
||||
if ($after < 0) {
|
||||
throw new \RuntimeException(__('Insufficient available points'));
|
||||
}
|
||||
|
||||
$model->user_asset_id = $userAssetId;
|
||||
$model->score_value = $scoreValue;
|
||||
$model->before = $before;
|
||||
$model->after = $after;
|
||||
$model->memo = $scoreValue > 0
|
||||
? __('Administrator added %s points', [abs($scoreValue)])
|
||||
: __('Administrator deducted %s points', [abs($scoreValue)]);
|
||||
|
||||
$userAsset->available_points = $after;
|
||||
$userAsset->save();
|
||||
}
|
||||
|
||||
public static function onBeforeDelete(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function userAsset(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(MallUserAsset::class, 'user_asset_id');
|
||||
}
|
||||
|
||||
public function admin(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Admin::class, 'admin_id');
|
||||
}
|
||||
}
|
||||
30
app/admin/validate/MallPointsLog.php
Normal file
30
app/admin/validate/MallPointsLog.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class MallPointsLog extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
protected $rule = [
|
||||
'user_asset_id' => 'require|integer|gt:0',
|
||||
'score_value' => 'require|integer|notIn:0',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['user_asset_id', 'score_value'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->field = [
|
||||
'user_asset_id' => __('User asset'),
|
||||
'score_value' => __('Points adjustment'),
|
||||
];
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
@@ -847,7 +847,7 @@ class Playx extends Api
|
||||
}
|
||||
|
||||
/**
|
||||
* 积分流水(领取/兑换/退回)
|
||||
* 积分流水(领取/兑换/退回/管理员调整)
|
||||
* GET /api/v1/mall/pointsLogs
|
||||
*
|
||||
* 鉴权:token / session_id / user_id(同 assets/orders)
|
||||
@@ -981,6 +981,28 @@ FROM (
|
||||
FROM mall_order o
|
||||
LEFT JOIN mall_item i ON i.id = o.mall_item_id
|
||||
WHERE o.user_id = :user_id_refund AND o.status = 'REJECTED' AND o.points_cost > 0 AND o.update_time IS NOT NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
'ADMIN_ADJUST' AS biz_type,
|
||||
CASE WHEN pl.score_value > 0 THEN 'IN' ELSE 'OUT' END AS direction,
|
||||
ABS(pl.score_value) AS points,
|
||||
pl.create_time AS ts,
|
||||
pl.id AS ref_id,
|
||||
'' AS order_no,
|
||||
'' AS order_status,
|
||||
0 AS item_id,
|
||||
'' AS item_title,
|
||||
0 AS item_type,
|
||||
0 AS item_score,
|
||||
0 AS item_amount,
|
||||
0 AS item_multiplier,
|
||||
'' AS item_category,
|
||||
'' AS item_category_title,
|
||||
CONCAT(LPAD(pl.create_time, 10, '0'), '_4_', LPAD(pl.id, 10, '0')) AS sort_key
|
||||
FROM mall_points_log pl
|
||||
WHERE pl.user_asset_id = :user_asset_id_adjust
|
||||
) t
|
||||
WHERE 1=1
|
||||
SQL;
|
||||
@@ -989,6 +1011,7 @@ SQL;
|
||||
'user_id_claim' => $playxUserId,
|
||||
'user_id_redeem' => $playxUserId,
|
||||
'user_id_refund' => $playxUserId,
|
||||
'user_asset_id_adjust' => $assetId,
|
||||
];
|
||||
|
||||
if ($cursor !== '') {
|
||||
@@ -1016,10 +1039,18 @@ SQL;
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$memo = '';
|
||||
if (($row['biz_type'] ?? '') === 'ADMIN_ADJUST') {
|
||||
$memoKey = ($row['direction'] ?? '') === 'OUT'
|
||||
? 'Administrator deducted %s points'
|
||||
: 'Administrator added %s points';
|
||||
$memo = __($memoKey, [$row['points'] ?? 0]);
|
||||
}
|
||||
$list[] = [
|
||||
'biz_type' => $row['biz_type'] ?? '',
|
||||
'direction' => $row['direction'] ?? '',
|
||||
'points' => $row['points'] ?? 0,
|
||||
'memo' => $memo,
|
||||
'ts' => $row['ts'] ?? null,
|
||||
'ref_id' => $row['ref_id'] ?? '',
|
||||
'order_no' => $row['order_no'] ?? '',
|
||||
|
||||
@@ -78,6 +78,8 @@ return [
|
||||
'item_id and user_id/session_id required' => 'item_id and user_id/session_id/token are required',
|
||||
'Item not found or not available' => 'Item not found or not available',
|
||||
'Insufficient points' => 'Insufficient points',
|
||||
'Administrator added %s points' => 'Administrator added %s points',
|
||||
'Administrator deducted %s points' => 'Administrator deducted %s points',
|
||||
'Redeem submitted, please wait about 10 minutes' => 'Redeem submitted, please wait about 10 minutes',
|
||||
'Missing required fields' => 'Missing required fields',
|
||||
'Out of stock' => 'Out of stock',
|
||||
|
||||
@@ -79,6 +79,8 @@ return [
|
||||
'item_id and user_id/session_id required' => 'item_id dan user_id/session_id/token diperlukan',
|
||||
'Item not found or not available' => 'Item tidak dijumpai atau tidak tersedia',
|
||||
'Insufficient points' => 'Mata tidak mencukupi',
|
||||
'Administrator added %s points' => 'Pentadbir menambah %s mata',
|
||||
'Administrator deducted %s points' => 'Pentadbir menolak %s mata',
|
||||
'Redeem submitted, please wait about 10 minutes' => 'Penebusan dihantar, sila tunggu kira-kira 10 minit',
|
||||
'Missing required fields' => 'Medan wajib tiada',
|
||||
'Out of stock' => 'Stok tidak mencukupi',
|
||||
|
||||
@@ -80,6 +80,8 @@ return [
|
||||
'item_id and user_id/session_id required' => '缺少 item_id,或未提供有效的 user_id/session_id/token',
|
||||
'Item not found or not available' => '商品不存在或已下架',
|
||||
'Insufficient points' => '积分不足',
|
||||
'Administrator added %s points' => '管理员增加积分 %s',
|
||||
'Administrator deducted %s points' => '管理员扣除积分 %s',
|
||||
'Redeem submitted, please wait about 10 minutes' => '兑换已提交,请等待约 10 分钟',
|
||||
'Missing required fields' => '缺少必填字段',
|
||||
'Out of stock' => '库存不足',
|
||||
|
||||
18
app/common/model/MallPointsLog.php
Normal file
18
app/common/model/MallPointsLog.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use app\common\model\traits\TimestampInteger;
|
||||
use support\think\Model;
|
||||
|
||||
class MallPointsLog extends Model
|
||||
{
|
||||
use TimestampInteger;
|
||||
|
||||
protected string $table = 'mall_points_log';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected bool $updateTime = false;
|
||||
}
|
||||
13
web/src/api/backend/mall/pointsLog.ts
Normal file
13
web/src/api/backend/mall/pointsLog.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import createAxios from '/@/utils/axios'
|
||||
|
||||
export const url = '/admin/mall.PointsLog/'
|
||||
|
||||
export function getUserAsset(userAssetId: string | number) {
|
||||
return createAxios({
|
||||
url: url + 'add',
|
||||
method: 'get',
|
||||
params: {
|
||||
userAssetId,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -19,6 +19,10 @@ export default {
|
||||
title: 'About this page',
|
||||
desc: 'History of users moving points from locked/pending to available, filterable by time and user.',
|
||||
},
|
||||
pointsLog: {
|
||||
title: 'About this page',
|
||||
desc: 'Records administrator additions and deductions to available points. Entries can only be added and viewed.',
|
||||
},
|
||||
item: {
|
||||
title: 'About this page',
|
||||
desc: 'Manage catalog items, stock, and multilingual display text for the points mall.',
|
||||
|
||||
20
web/src/lang/backend/en/mall/pointsLog.ts
Normal file
20
web/src/lang/backend/en/mall/pointsLog.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
id: 'ID',
|
||||
user_asset: 'User',
|
||||
user_asset_id: 'User asset ID',
|
||||
playx_user_id: 'PlayX ID',
|
||||
username: 'Username',
|
||||
available_points: 'Available points',
|
||||
current_points: 'Current points',
|
||||
score_value: 'Points adjustment',
|
||||
before: 'Before',
|
||||
after: 'After',
|
||||
memo: 'Remark',
|
||||
admin: 'Administrator',
|
||||
create_time: 'Created at',
|
||||
quick_search_fields: 'PlayX ID, username, phone',
|
||||
non_zero_integer_required: 'Please enter a non-zero integer',
|
||||
insufficient_available_points: 'Insufficient available points',
|
||||
admin_add_memo: 'Administrator added {value} points',
|
||||
admin_deduct_memo: 'Administrator deducted {value} points',
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
playx_user_id: 'playX_user_id',
|
||||
locked_points: 'locked_points',
|
||||
available_points: 'available_points',
|
||||
adjust_available_points: 'Adjust points',
|
||||
today_limit: 'today_limit',
|
||||
today_claimed: 'today_claimed',
|
||||
today_limit_date: 'today_limit_date',
|
||||
@@ -12,4 +13,3 @@ export default {
|
||||
update_time: 'update_time',
|
||||
'quick Search Fields': 'id, playX_user_id, username, phone',
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,9 @@ export default {
|
||||
mall_order_approve: 'Approve',
|
||||
mall_dailyPush: 'Daily push',
|
||||
mall_claimLog: 'Claim log',
|
||||
mall_pointsLog: 'Points management',
|
||||
mall_pointsLog_index: 'Browse',
|
||||
mall_pointsLog_add: 'Add',
|
||||
mall_item: 'Products',
|
||||
mall_playxOrder: 'playX orders',
|
||||
mall_playxCenter: 'playX center',
|
||||
|
||||
@@ -19,6 +19,10 @@ export default {
|
||||
title: '页面说明',
|
||||
desc: '用户从待领取积分划转至可用积分的操作流水,可按时间与用户追溯。',
|
||||
},
|
||||
pointsLog: {
|
||||
title: '页面说明',
|
||||
desc: '记录管理员对用户可用积分的人工增加与扣除;流水仅允许新增和查看。',
|
||||
},
|
||||
item: {
|
||||
title: '页面说明',
|
||||
desc: '维护积分商城上架商品、库存与多语言展示信息。',
|
||||
|
||||
20
web/src/lang/backend/zh-cn/mall/pointsLog.ts
Normal file
20
web/src/lang/backend/zh-cn/mall/pointsLog.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
id: 'ID',
|
||||
user_asset: '用户',
|
||||
user_asset_id: '用户资产ID',
|
||||
playx_user_id: 'playX-ID',
|
||||
username: '用户名',
|
||||
available_points: '可用积分',
|
||||
current_points: '当前积分',
|
||||
score_value: '变更积分',
|
||||
before: '调整前积分',
|
||||
after: '调整后积分',
|
||||
memo: '备注',
|
||||
admin: '操作管理员',
|
||||
create_time: '创建时间',
|
||||
quick_search_fields: 'playX用户ID、用户名、手机号',
|
||||
non_zero_integer_required: '请输入非零整数',
|
||||
insufficient_available_points: '可用积分不足',
|
||||
admin_add_memo: '管理员增加积分 {value}',
|
||||
admin_deduct_memo: '管理员扣除积分 {value}',
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export default {
|
||||
playx_user_id: 'playX-ID',
|
||||
locked_points: '待领取积分',
|
||||
available_points: '可用积分',
|
||||
adjust_available_points: '调整积分',
|
||||
today_limit: '今日可领取上限',
|
||||
today_claimed: '今日已领取',
|
||||
today_limit_date: '今日上限日期',
|
||||
@@ -12,4 +13,3 @@ export default {
|
||||
update_time: '修改时间',
|
||||
'quick Search Fields': 'ID、playX用户ID、用户名、手机号',
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ export default {
|
||||
mall_order_approve: '审核通过',
|
||||
mall_dailyPush: '每日推送',
|
||||
mall_claimLog: '领取记录',
|
||||
mall_pointsLog: '积分管理',
|
||||
mall_pointsLog_index: '查看',
|
||||
mall_pointsLog_add: '新增',
|
||||
mall_item: '商品管理',
|
||||
mall_playxOrder: 'playX订单',
|
||||
mall_playxCenter: 'playX中心',
|
||||
|
||||
150
web/src/views/backend/mall/pointsLog/index.vue
Normal file
150
web/src/views/backend/mall/pointsLog/index.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<MallPageIntro page-key="pointsLog" />
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('mall.pointsLog.quick_search_fields') })"
|
||||
>
|
||||
<el-button v-if="state.userAsset.id" v-blur class="table-header-operate">
|
||||
<span class="table-header-operate-text">
|
||||
{{
|
||||
state.userAsset.username +
|
||||
' (ID:' +
|
||||
state.userAsset.id +
|
||||
') ' +
|
||||
t('mall.pointsLog.available_points') +
|
||||
': ' +
|
||||
state.userAsset.available_points
|
||||
}}
|
||||
</span>
|
||||
</el-button>
|
||||
</TableHeader>
|
||||
|
||||
<Table />
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { debounce } from 'lodash-es'
|
||||
import { provide, reactive, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getUserAsset, url } from '/@/api/backend/mall/pointsLog'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import MallPageIntro from '/@/components/mall/MallPageIntro.vue'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
import PopupForm from './popupForm.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'mall/pointsLog',
|
||||
})
|
||||
|
||||
interface UserAssetInfo {
|
||||
id?: number
|
||||
username?: string
|
||||
phone?: string
|
||||
playx_user_id?: string
|
||||
available_points?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const routeUserAssetId = route.query.user_asset_id
|
||||
const defaultUserAssetId = typeof routeUserAssetId === 'string' ? routeUserAssetId : ''
|
||||
const state = reactive<{ userAsset: UserAssetInfo }>({
|
||||
userAsset: {},
|
||||
})
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi(url),
|
||||
{
|
||||
column: [
|
||||
{ label: t('mall.pointsLog.id'), prop: 'id', align: 'center', operator: '=', width: 70 },
|
||||
{ label: t('mall.pointsLog.user_asset_id'), prop: 'user_asset_id', align: 'center', operator: '=', width: 100 },
|
||||
{
|
||||
label: t('mall.pointsLog.playx_user_id'),
|
||||
prop: 'userAsset.playx_user_id',
|
||||
align: 'center',
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('mall.pointsLog.username'),
|
||||
prop: 'userAsset.username',
|
||||
align: 'center',
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{ label: t('mall.pointsLog.score_value'), prop: 'score_value', align: 'center', operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('mall.pointsLog.before'), prop: 'before', align: 'center', operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('mall.pointsLog.after'), prop: 'after', align: 'center', operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('mall.pointsLog.memo'),
|
||||
prop: 'memo',
|
||||
align: 'center',
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{ label: t('mall.pointsLog.admin'), prop: 'admin.username', align: 'center', operator: 'LIKE' },
|
||||
{
|
||||
label: t('mall.pointsLog.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
sortable: 'custom',
|
||||
operator: 'RANGE',
|
||||
width: 160,
|
||||
},
|
||||
],
|
||||
dblClickNotEditColumn: ['all'],
|
||||
},
|
||||
{
|
||||
defaultItems: {
|
||||
user_asset_id: defaultUserAssetId,
|
||||
score_value: 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const getUserAssetInfo = debounce((userAssetId: unknown) => {
|
||||
if ((typeof userAssetId !== 'string' && typeof userAssetId !== 'number') || userAssetId === '') {
|
||||
state.userAsset = {}
|
||||
return
|
||||
}
|
||||
|
||||
getUserAsset(userAssetId).then((response) => {
|
||||
state.userAsset = response.data.userAsset
|
||||
})
|
||||
}, 300)
|
||||
|
||||
baTable.after.onSubmit = () => {
|
||||
getUserAssetInfo(baTable.comSearch.form.user_asset_id)
|
||||
}
|
||||
baTable.after.onTableHeaderAction = ({ event }) => {
|
||||
if (event == 'refresh') {
|
||||
getUserAssetInfo(baTable.comSearch.form.user_asset_id)
|
||||
}
|
||||
}
|
||||
|
||||
baTable.mount()
|
||||
baTable.getData()
|
||||
provide('baTable', baTable)
|
||||
|
||||
getUserAssetInfo(baTable.comSearch.form.user_asset_id)
|
||||
|
||||
watch(
|
||||
() => baTable.comSearch.form.user_asset_id,
|
||||
(newValue) => {
|
||||
baTable.form.defaultItems!.user_asset_id = newValue
|
||||
getUserAssetInfo(newValue)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
173
web/src/views/backend/mall/pointsLog/popupForm.vue
Normal file
173
web/src/views/backend/mall/pointsLog/popupForm.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="baTable.form.operate == 'Add'" @close="baTable.toggleForm">
|
||||
<template #header>
|
||||
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
||||
{{ baTable.form.operate ? t(baTable.form.operate) : '' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-scrollbar class="ba-table-form-scrollbar">
|
||||
<div
|
||||
class="ba-operate-form"
|
||||
:class="'ba-' + baTable.form.operate + '-form'"
|
||||
:style="config.layout.shrink ? '' : 'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
|
||||
>
|
||||
<el-form
|
||||
v-if="!baTable.form.loading"
|
||||
ref="formRef"
|
||||
:model="baTable.form.items"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="rules"
|
||||
@keyup.enter="baTable.onSubmit(formRef)"
|
||||
>
|
||||
<FormItem
|
||||
type="remoteSelect"
|
||||
prop="user_asset_id"
|
||||
:label="t('mall.pointsLog.user_asset')"
|
||||
v-model="baTable.form.items!.user_asset_id"
|
||||
:placeholder="t('Click select')"
|
||||
:input-attr="{
|
||||
pk: 'id',
|
||||
field: 'username',
|
||||
remoteUrl: '/admin/mall.UserAsset/select',
|
||||
onChange: getUserAssetInfo,
|
||||
}"
|
||||
/>
|
||||
<el-form-item :label="t('mall.pointsLog.playx_user_id')">
|
||||
<el-input v-model="state.userAsset.playx_user_id" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.pointsLog.username')">
|
||||
<el-input v-model="state.userAsset.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.pointsLog.current_points')">
|
||||
<el-input v-model="state.userAsset.available_points" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item prop="score_value" :label="t('mall.pointsLog.score_value')">
|
||||
<el-input-number
|
||||
v-model="baTable.form.items!.score_value"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
@change="changeScore"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.pointsLog.after')">
|
||||
<el-input v-model="state.after" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.pointsLog.memo')">
|
||||
<el-input v-model="state.memo" type="textarea" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<template #footer>
|
||||
<div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
|
||||
<el-button @click="baTable.toggleForm('')">{{ t('Cancel') }}</el-button>
|
||||
<el-button v-blur :loading="baTable.form.submitLoading" type="primary" @click="baTable.onSubmit(formRef)">
|
||||
{{ t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getUserAsset } from '/@/api/backend/mall/pointsLog'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
import { buildValidatorData } from '/@/utils/validate'
|
||||
|
||||
interface UserAssetInfo {
|
||||
id?: number
|
||||
username?: string
|
||||
playx_user_id?: string
|
||||
available_points?: number
|
||||
}
|
||||
|
||||
const config = useConfig()
|
||||
const { t } = useI18n()
|
||||
const injectedBaTable = inject<baTableClass>('baTable')
|
||||
if (!injectedBaTable) {
|
||||
throw new Error('baTable is not provided')
|
||||
}
|
||||
const baTable = injectedBaTable
|
||||
const formRef = useTemplateRef('formRef')
|
||||
|
||||
const state = reactive<{
|
||||
userAsset: UserAssetInfo
|
||||
after: number
|
||||
memo: string
|
||||
}>({
|
||||
userAsset: {},
|
||||
after: 0,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
const validateScoreValue: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (typeof value !== 'number' || !Number.isInteger(value) || value === 0) {
|
||||
callback(new Error(t('mall.pointsLog.non_zero_integer_required')))
|
||||
return
|
||||
}
|
||||
if ((state.userAsset.available_points ?? 0) + value < 0) {
|
||||
callback(new Error(t('mall.pointsLog.insufficient_available_points')))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
user_asset_id: [buildValidatorData({ name: 'required', title: t('mall.pointsLog.user_asset') })],
|
||||
score_value: [
|
||||
{
|
||||
validator: validateScoreValue,
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const changeScore = (value: number | undefined) => {
|
||||
const scoreValue = value ?? 0
|
||||
const availablePoints = state.userAsset.available_points ?? 0
|
||||
state.after = availablePoints + scoreValue
|
||||
if (scoreValue > 0) {
|
||||
state.memo = t('mall.pointsLog.admin_add_memo', { value: Math.abs(scoreValue) })
|
||||
} else if (scoreValue < 0) {
|
||||
state.memo = t('mall.pointsLog.admin_deduct_memo', { value: Math.abs(scoreValue) })
|
||||
} else {
|
||||
state.memo = ''
|
||||
}
|
||||
}
|
||||
|
||||
const getUserAssetInfo = () => {
|
||||
const userAssetId = baTable.form.items!.user_asset_id
|
||||
if ((typeof userAssetId !== 'string' && typeof userAssetId !== 'number') || userAssetId === '') {
|
||||
state.userAsset = {}
|
||||
state.after = 0
|
||||
state.memo = ''
|
||||
return
|
||||
}
|
||||
|
||||
getUserAsset(userAssetId).then((response) => {
|
||||
state.userAsset = response.data.userAsset
|
||||
changeScore(baTable.form.items!.score_value)
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => baTable.form.operate,
|
||||
(operate) => {
|
||||
if (operate == 'Add') {
|
||||
getUserAssetInfo()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -27,7 +27,13 @@
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="rules"
|
||||
>
|
||||
<FormItem :label="t('mall.userAsset.id')" type="number" v-model="baTable.form.items!.id" prop="id" :input-attr="{ disabled: true }" />
|
||||
<FormItem
|
||||
:label="t('mall.userAsset.id')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.id"
|
||||
prop="id"
|
||||
:input-attr="{ disabled: true }"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('mall.userAsset.playx_user_id')"
|
||||
type="string"
|
||||
@@ -54,17 +60,15 @@
|
||||
type="number"
|
||||
v-model="baTable.form.items!.locked_points"
|
||||
prop="locked_points"
|
||||
:input-attr="{ step: 1, min: 0 }"
|
||||
:placeholder="t('Please input field', { field: t('mall.userAsset.locked_points') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('mall.userAsset.available_points')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.available_points"
|
||||
prop="available_points"
|
||||
:input-attr="{ step: 1, min: 0 }"
|
||||
:placeholder="t('Please input field', { field: t('mall.userAsset.available_points') })"
|
||||
:input-attr="{ disabled: true }"
|
||||
/>
|
||||
<el-form-item :label="t('mall.userAsset.available_points')">
|
||||
<el-input v-model="baTable.form.items!.available_points" readonly>
|
||||
<template #append>
|
||||
<el-button @click="changeAvailablePoints">{{ t('mall.userAsset.adjust_available_points') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<FormItem
|
||||
:label="t('mall.userAsset.today_limit')"
|
||||
type="number"
|
||||
@@ -78,8 +82,7 @@
|
||||
type="number"
|
||||
v-model="baTable.form.items!.today_claimed"
|
||||
prop="today_claimed"
|
||||
:input-attr="{ step: 1, min: 0 }"
|
||||
:placeholder="t('Please input field', { field: t('mall.userAsset.today_claimed') })"
|
||||
:input-attr="{ disabled: true }"
|
||||
/>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -104,8 +107,10 @@ import baTableClass from '/@/utils/baTable'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { buildValidatorData } from '/@/utils/validate'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const config = useConfig()
|
||||
const router = useRouter()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
@@ -118,7 +123,17 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
today_limit: [buildValidatorData({ name: 'required', title: t('mall.userAsset.today_limit') })],
|
||||
today_claimed: [buildValidatorData({ name: 'required', title: t('mall.userAsset.today_claimed') })],
|
||||
})
|
||||
|
||||
const changeAvailablePoints = () => {
|
||||
const userAssetId = baTable.form.items!.id
|
||||
baTable.toggleForm()
|
||||
router.push({
|
||||
name: 'mall/pointsLog',
|
||||
query: {
|
||||
user_asset_id: userAssetId,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user