Compare commits
10 Commits
c213b2d9e9
...
fb16321b7e
| Author | SHA1 | Date | |
|---|---|---|---|
| fb16321b7e | |||
| 7e695d32cf | |||
| 080ead200b | |||
| 0b0c0ee04a | |||
| 518642ddb0 | |||
| 569f9e7749 | |||
| f88afbcadb | |||
| 92545067d2 | |||
| 6bca594769 | |||
| 56df105af6 |
34
app/admin/controller/agent/CommissionRecord.php
Normal file
34
app/admin/controller/agent/CommissionRecord.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\agent;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 代理佣金记录
|
||||
*/
|
||||
class CommissionRecord extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['id', 'create_time', 'update_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'remark'];
|
||||
|
||||
protected string|array $defaultSortField = ['id' => 'desc'];
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected array $withJoinTable = ['settlementPeriod', 'channel', 'admin'];
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\AgentCommissionRecord();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
32
app/admin/controller/agent/SettlementPeriod.php
Normal file
32
app/admin/controller/agent/SettlementPeriod.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\agent;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 代理结算周期
|
||||
*/
|
||||
class SettlementPeriod extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['id', 'create_time', 'update_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'settlement_no', 'remark'];
|
||||
|
||||
protected string|array $defaultSortField = ['id' => 'desc'];
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\AgentSettlementPeriod();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
64
app/admin/controller/config/GameConfig.php
Normal file
64
app/admin/controller/config/GameConfig.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\config;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\game\ZiHuaDictionary as ZiHuaDictionaryLib;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 游戏配置(game_config)
|
||||
*/
|
||||
class GameConfig extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected string|array $preExcludeFields = ['id', 'create_time', 'update_time'];
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'config_key', 'remark'];
|
||||
|
||||
protected string|array $defaultSortField = ['id' => 'asc'];
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'asc'];
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\GameConfig();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表:排除独立表单维护的 36 字花字典
|
||||
*/
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
return $this->select($this->request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '') {
|
||||
$where[] = [$mainShort . '.config_key', '<>', ZiHuaDictionaryLib::CONFIG_KEY];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
->field($this->indexField)
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
130
app/admin/controller/config/ZiHuaDictionary.php
Normal file
130
app/admin/controller/config/ZiHuaDictionary.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\config;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\game\ZiHuaDictionary as ZiHuaDictionaryLib;
|
||||
use InvalidArgumentException;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Throwable;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 36 字花字典独立编辑(仅 game_config.zi_hua_36_dictionary)
|
||||
*/
|
||||
class ZiHuaDictionary extends Backend
|
||||
{
|
||||
protected bool $modelValidate = false;
|
||||
protected array $noNeedPermission = ['index', 'save'];
|
||||
|
||||
private function hasNodePermission(WebmanRequest $request, string $action): bool
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return false;
|
||||
}
|
||||
$controllerPath = get_controller_path($request);
|
||||
if (!$controllerPath) {
|
||||
return false;
|
||||
}
|
||||
$paths = [];
|
||||
$paths[] = $controllerPath . '/' . $action;
|
||||
$parts = explode('/', $controllerPath);
|
||||
foreach ($parts as &$part) {
|
||||
if (str_contains($part, '_')) {
|
||||
$part = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $part))));
|
||||
}
|
||||
}
|
||||
$paths[] = implode('/', $parts) . '/' . $action;
|
||||
foreach (array_values(array_unique($paths)) as $path) {
|
||||
if ($this->auth->check($path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET:读取 36 条;POST 不支持
|
||||
*/
|
||||
public function index(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->hasNodePermission($request, 'index')) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
if ($request->method() !== 'GET') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$row = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
$items = ZiHuaDictionaryLib::parseFromConfigValue($row['config_value'] ?? null);
|
||||
return $this->success('', [
|
||||
'items' => $items,
|
||||
'categories' => ZiHuaDictionaryLib::CATEGORIES,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 JSON 数组(仅 value_type=json)
|
||||
*/
|
||||
public function save(WebmanRequest $request): Response
|
||||
{
|
||||
$response = $this->initializeBackend($request);
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->hasNodePermission($request, 'save')) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
$payload = $request->post();
|
||||
if (!is_array($payload)) {
|
||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||
}
|
||||
$items = $payload['items'] ?? null;
|
||||
if (!is_array($items)) {
|
||||
return $this->error('items 必须为数组');
|
||||
}
|
||||
try {
|
||||
$clean = ZiHuaDictionaryLib::prepareItemsForSave($items);
|
||||
$json = ZiHuaDictionaryLib::encodeForDb($clean);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$now = time();
|
||||
try {
|
||||
$exists = Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->find();
|
||||
if ($exists) {
|
||||
Db::name('game_config')->where('config_key', ZiHuaDictionaryLib::CONFIG_KEY)->update([
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} else {
|
||||
Db::name('game_config')->insert([
|
||||
'config_key' => ZiHuaDictionaryLib::CONFIG_KEY,
|
||||
'config_value' => $json,
|
||||
'value_type' => 'json',
|
||||
'remark' => '36字花字典 JSON 数组(独立表单维护)',
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success(__('Saved successfully'));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,19 @@ use Webman\Http\Request as WebmanRequest;
|
||||
class ZiHuaDictionary extends Backend
|
||||
{
|
||||
protected bool $modelValidate = false;
|
||||
protected array $noNeedPermission = ['index', 'save'];
|
||||
|
||||
private function hasNodePermission(WebmanRequest $request, string $action): bool
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return false;
|
||||
}
|
||||
$controllerPath = get_controller_path($request);
|
||||
if (!$controllerPath) {
|
||||
return false;
|
||||
}
|
||||
return $this->auth->check($controllerPath . '/' . $action);
|
||||
}
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
@@ -31,6 +44,9 @@ class ZiHuaDictionary extends Backend
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->hasNodePermission($request, 'index')) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
if ($request->method() !== 'GET') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
@@ -51,6 +67,9 @@ class ZiHuaDictionary extends Backend
|
||||
if ($response !== null) {
|
||||
return $response;
|
||||
}
|
||||
if (!$this->hasNodePermission($request, 'save')) {
|
||||
return $this->error(__('You have no permission'), [], 401);
|
||||
}
|
||||
if ($request->method() !== 'POST') {
|
||||
return $this->error(__('Parameter error'));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\game;
|
||||
namespace app\admin\controller\order;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
@@ -22,11 +22,11 @@ class BetOrder extends Backend
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected array $withJoinTable = ['gameUser', 'channel', 'gamePeriod'];
|
||||
protected array $withJoinTable = ['user', 'channel', 'gamePeriod'];
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\GameBetOrder();
|
||||
$this->model = new \app\common\model\BetOrder();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class BetOrder extends Backend
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'gameUser' => ['username', 'phone'],
|
||||
'user' => ['username', 'phone'],
|
||||
'channel' => ['name'],
|
||||
'gamePeriod' => ['period_no', 'status'],
|
||||
])
|
||||
86
app/admin/controller/order/DepositOrder.php
Normal file
86
app/admin/controller/order/DepositOrder.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\order;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 充值订单
|
||||
*/
|
||||
class DepositOrder extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'order_no', 'pay_channel', 'remark'];
|
||||
|
||||
protected string|array $defaultSortField = ['id' => 'desc'];
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected array $withJoinTable = ['user', 'channel'];
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\DepositOrder();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
return $this->select($this->request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'user' => ['username', 'phone'],
|
||||
'channel' => ['name'],
|
||||
])
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')->field(['id', 'channel_id'])->where('id', $this->auth->id)->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
$owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id');
|
||||
$created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id');
|
||||
return array_values(array_unique(array_merge($ids, $owned, $created)));
|
||||
}
|
||||
}
|
||||
87
app/admin/controller/order/WithdrawOrder.php
Normal file
87
app/admin/controller/order/WithdrawOrder.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\order;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
use support\Response;
|
||||
use Webman\Http\Request as WebmanRequest;
|
||||
|
||||
/**
|
||||
* 提现订单
|
||||
*/
|
||||
class WithdrawOrder extends Backend
|
||||
{
|
||||
protected ?object $model = null;
|
||||
|
||||
protected bool $modelValidate = false;
|
||||
|
||||
protected string|array $quickSearchField = ['id', 'order_no', 'remark'];
|
||||
|
||||
protected string|array $defaultSortField = ['id' => 'desc'];
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected array $withJoinTable = ['user', 'channel', 'reviewAdmin'];
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\WithdrawOrder();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function _index(): Response
|
||||
{
|
||||
if ($this->request && $this->request->get('select')) {
|
||||
return $this->select($this->request);
|
||||
}
|
||||
|
||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||
$table = strtolower($this->model->getTable());
|
||||
$mainShort = $alias[$table] ?? '';
|
||||
if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) {
|
||||
$channelIds = $this->getScopedChannelIdsForFilter();
|
||||
$where[] = [$mainShort . '.channel_id', 'in', $channelIds !== [] ? $channelIds : [0]];
|
||||
}
|
||||
|
||||
$res = $this->model
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'user' => ['username', 'phone'],
|
||||
'channel' => ['name'],
|
||||
'reviewAdmin' => ['username'],
|
||||
])
|
||||
->alias($alias)
|
||||
->where($where)
|
||||
->order($order)
|
||||
->paginate($limit);
|
||||
|
||||
return $this->success('', [
|
||||
'list' => $res->items(),
|
||||
'total' => $res->total(),
|
||||
'remark' => get_route_remark(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getScopedChannelIdsForFilter(): array
|
||||
{
|
||||
if (!$this->auth) {
|
||||
return [0];
|
||||
}
|
||||
if ($this->auth->isSuperAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$admin = Db::name('admin')->field(['id', 'channel_id'])->where('id', $this->auth->id)->find();
|
||||
$ids = [];
|
||||
if ($admin && !empty($admin['channel_id'])) {
|
||||
$ids[] = $admin['channel_id'];
|
||||
}
|
||||
$owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id');
|
||||
$created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id');
|
||||
return array_values(array_unique(array_merge($ids, $owned, $created)));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\game;
|
||||
namespace app\admin\controller\record;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use support\think\Db;
|
||||
@@ -22,11 +22,11 @@ class UserWalletRecord extends Backend
|
||||
|
||||
protected string|array $orderGuarantee = ['id' => 'desc'];
|
||||
|
||||
protected array $withJoinTable = ['gameUser', 'channel', 'operatorAdmin'];
|
||||
protected array $withJoinTable = ['user', 'channel', 'operatorAdmin'];
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\GameUserWalletRecord();
|
||||
$this->model = new \app\common\model\UserWalletRecord();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class UserWalletRecord extends Backend
|
||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||
->with($this->withJoinTable)
|
||||
->visible([
|
||||
'gameUser' => ['username', 'phone'],
|
||||
'user' => ['username', 'phone'],
|
||||
'channel' => ['name'],
|
||||
'operatorAdmin' => ['username'],
|
||||
])
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\game;
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use Throwable;
|
||||
use app\common\controller\Backend;
|
||||
@@ -14,9 +14,9 @@ use Webman\Http\Request as WebmanRequest;
|
||||
class User extends Backend
|
||||
{
|
||||
/**
|
||||
* GameUser模型对象
|
||||
* User模型对象
|
||||
* @var object|null
|
||||
* @phpstan-var \app\common\model\GameUser|null
|
||||
* @phpstan-var \app\common\model\User|null
|
||||
*/
|
||||
protected ?object $model = null;
|
||||
|
||||
@@ -28,7 +28,7 @@ class User extends Backend
|
||||
|
||||
protected function initController(WebmanRequest $request): ?Response
|
||||
{
|
||||
$this->model = new \app\common\model\GameUser();
|
||||
$this->model = new \app\common\model\User();
|
||||
return null;
|
||||
}
|
||||
|
||||
38
app/common/model/AgentCommissionRecord.php
Normal file
38
app/common/model/AgentCommissionRecord.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class AgentCommissionRecord extends Model
|
||||
{
|
||||
protected $name = 'agent_commission_record';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'settled_at' => 'integer',
|
||||
'commission_rate' => 'string',
|
||||
'calc_base_amount' => 'string',
|
||||
'commission_amount' => 'string',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Channel::class, 'channel_id', 'id');
|
||||
}
|
||||
|
||||
public function admin(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\app\admin\model\Admin::class, 'admin_id', 'id');
|
||||
}
|
||||
|
||||
public function settlementPeriod(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AgentSettlementPeriod::class, 'settlement_period_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
24
app/common/model/AgentSettlementPeriod.php
Normal file
24
app/common/model/AgentSettlementPeriod.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class AgentSettlementPeriod extends Model
|
||||
{
|
||||
protected $name = 'agent_settlement_period';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'period_start_at' => 'integer',
|
||||
'period_end_at' => 'integer',
|
||||
'total_bet_amount' => 'string',
|
||||
'total_payout_amount' => 'string',
|
||||
'platform_profit_amount' => 'string',
|
||||
'status' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class GameBetOrder extends Model
|
||||
class BetOrder extends Model
|
||||
{
|
||||
protected $name = 'game_bet_order';
|
||||
protected $name = 'bet_order';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
@@ -24,9 +24,9 @@ class GameBetOrder extends Model
|
||||
'is_auto' => 'integer',
|
||||
];
|
||||
|
||||
public function gameUser(): \think\model\relation\BelongsTo
|
||||
public function user(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GameUser::class, 'user_id', 'id');
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
@@ -39,3 +39,4 @@ class GameBetOrder extends Model
|
||||
return $this->belongsTo(GamePeriod::class, 'period_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
30
app/common/model/DepositOrder.php
Normal file
30
app/common/model/DepositOrder.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class DepositOrder extends Model
|
||||
{
|
||||
protected $name = 'deposit_order';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'pay_time' => 'integer',
|
||||
'amount' => 'string',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Channel::class, 'channel_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
/**
|
||||
* GameUser
|
||||
*/
|
||||
class GameUser extends Model
|
||||
{
|
||||
// 表名
|
||||
protected $name = 'game_user';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
// 字段类型转换(金额 decimal(18,4) 用字符串避免浮点误差)
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'coin' => 'string',
|
||||
'total_deposit_coin' => 'string',
|
||||
'total_valid_bet_coin' => 'string',
|
||||
'risk_flags' => 'integer',
|
||||
'current_streak' => 'integer',
|
||||
];
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\app\common\model\Channel::class, 'channel_id', 'id');
|
||||
}
|
||||
|
||||
public function admin(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\app\admin\model\Admin::class, 'admin_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -2,42 +2,34 @@
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use app\common\model\traits\TimestampInteger;
|
||||
use support\think\Model;
|
||||
|
||||
/**
|
||||
* 会员公共模型
|
||||
* 用户主表
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
use TimestampInteger;
|
||||
protected $name = 'user';
|
||||
|
||||
protected string $table = 'user';
|
||||
protected string $pk = 'id';
|
||||
protected bool $autoWriteTimestamp = true;
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
public function getAvatarAttr($value): string
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'coin' => 'string',
|
||||
'total_deposit_coin' => 'string',
|
||||
'total_valid_bet_coin' => 'string',
|
||||
'risk_flags' => 'integer',
|
||||
'current_streak' => 'integer',
|
||||
];
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return full_url($value, false, config('buildadmin.default_avatar'));
|
||||
return $this->belongsTo(Channel::class, 'channel_id', 'id');
|
||||
}
|
||||
|
||||
public function setAvatarAttr($value): string
|
||||
public function admin(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $value == full_url('', false, config('buildadmin.default_avatar')) ? '' : $value;
|
||||
}
|
||||
|
||||
public function resetPassword($uid, $newPassword)
|
||||
{
|
||||
return $this->where(['id' => $uid])->update(['password' => hash_password($newPassword), 'salt' => '']);
|
||||
}
|
||||
|
||||
public function getMoneyAttr($value): string
|
||||
{
|
||||
return bcdiv((string)$value, '100', 2);
|
||||
}
|
||||
|
||||
public function setMoneyAttr($value): string
|
||||
{
|
||||
return bcmul((string)$value, '100', 2);
|
||||
return $this->belongsTo(\app\admin\model\Admin::class, 'admin_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace app\common\model;
|
||||
use support\think\Model;
|
||||
|
||||
/**
|
||||
* 玩家游戏币钱包流水(只增不改;余额变更须与 game_user.coin 条件更新同事务)
|
||||
* 用户钱包流水(原 game_user_wallet_record)
|
||||
*/
|
||||
class GameUserWalletRecord extends Model
|
||||
class UserWalletRecord extends Model
|
||||
{
|
||||
protected $name = 'game_user_wallet_record';
|
||||
protected $name = 'user_wallet_record';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
@@ -24,9 +24,9 @@ class GameUserWalletRecord extends Model
|
||||
'balance_after' => 'string',
|
||||
];
|
||||
|
||||
public function gameUser(): \think\model\relation\BelongsTo
|
||||
public function user(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GameUser::class, 'user_id', 'id');
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
37
app/common/model/WithdrawOrder.php
Normal file
37
app/common/model/WithdrawOrder.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\think\Model;
|
||||
|
||||
class WithdrawOrder extends Model
|
||||
{
|
||||
protected $name = 'withdraw_order';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
protected $type = [
|
||||
'create_time' => 'integer',
|
||||
'update_time' => 'integer',
|
||||
'review_time' => 'integer',
|
||||
'amount' => 'string',
|
||||
'fee' => 'string',
|
||||
'actual_amount' => 'string',
|
||||
'status' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function channel(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Channel::class, 'channel_id', 'id');
|
||||
}
|
||||
|
||||
public function reviewAdmin(): \think\model\relation\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\app\admin\model\Admin::class, 'review_admin_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class GameUser extends Validate
|
||||
{
|
||||
protected $failException = true;
|
||||
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected $rule = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
protected $message = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected $scene = [
|
||||
'add' => [],
|
||||
'edit' => [],
|
||||
];
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
# 「36字花」数据库与实施计划
|
||||
|
||||
**文档版本**:V1.4
|
||||
**依据**:《36字花 PRD》《业务流程说明书》《后端系统规格书》及现有表 `game_user`
|
||||
**文档版本**:V1.10
|
||||
**依据**:《36字花 PRD》《业务流程说明书》《后端系统规格书》及现有表 `user`
|
||||
**目标**:明确分阶段落地步骤、需新建表、两表适配方向与可执行验证清单。
|
||||
|
||||
**DDL 脚本(可执行)**:[database/dfw_36zihua_schema_v1.sql](../database/dfw_36zihua_schema_v1.sql)
|
||||
内含:新建 `channel` 表,及 `game_user` / `admin` / `admin_group` 的 `ALTER` 语句,以及新建表完整 `CREATE TABLE`(字段级 `COMMENT` 即字段清单)。
|
||||
内含:新建 `channel` 表,及 `user` / `admin` / `admin_group` 的 `ALTER` 语句,以及新建表完整 `CREATE TABLE`(字段级 `COMMENT` 即字段清单)。
|
||||
|
||||
---
|
||||
|
||||
@@ -14,22 +14,93 @@
|
||||
| 表名 | 当前角色 | 目标角色 |
|
||||
|------|----------|----------|
|
||||
| `channel` | 新渠道主表 | **顶级代理渠道分红配置表**:由超管创建,维护分红方式、分红金额、总利润等渠道参数 |
|
||||
| `game_user` | 用户(coin、channel_id 等) | **C 端玩家主表**:余额与精度、归属代理、流水与风控字段 |
|
||||
| `user` | 用户(coin、channel_id 等) | **C 端玩家主表**:余额与精度、归属代理、流水与风控字段 |
|
||||
| `admin` | 后台管理员 | **子代理承载表**:顶级代理下级代理放在 `admin`,通过 `invite_code` 管理客户,统一无开奖控制权 |
|
||||
| `admin_group` | 角色组 | **按渠道隔离**:角色组归属 `channel`,渠道仅管理本渠道角色组 |
|
||||
|
||||
### 1.1 全局对局与渠道职责(重要)
|
||||
|
||||
与「多渠道」并存的一条硬规则:**对局与开奖全局唯一**,渠道**不**切分牌桌、**不**各自开奖。
|
||||
|
||||
| 维度 | 口径 |
|
||||
|------|------|
|
||||
| **期号 / 对局** | **全平台共用一套** `game_period`(及全网唯一的当前 `period_no`、同一套状态机)。不存在「每个渠道一局」或「每渠道独立期号表」。 |
|
||||
| **开奖结果** | **全渠道玩家共享同一期、同一 `result_number`**。所有玩家(无论来自哪个 `channel_id`)压的是**同一场**;避免用户感知「不同入口有不同局、可被渠道操纵」。 |
|
||||
| **渠道的用途** | `channel`、`user.channel_id`、注单上的 **`channel_id` 快照**(若有)仅用于 **代理归属、分润、风控与后台数据范围**,**不**用于生成多套并行对局或独立开奖。 |
|
||||
| **Redis / 接口** | 当前期倒计时、封盘、算票、开奖号码等 **热状态全局一份**;不得在业务上按渠道维护多套「当前期」或多套开奖结果。 |
|
||||
|
||||
> **与 DDL 的对应**:`game_period` **不设** `channel_id`;`bet_order.channel_id` 为下单时用户归属快照,不改变「全场一局」语义。详见 §11.5。
|
||||
|
||||
---
|
||||
|
||||
## 二、分阶段执行计划
|
||||
## 二、分阶段执行计划与「先核心、后细节」顺序
|
||||
|
||||
**原则**:先打通 **账号 / 渠道 / 余额与流水 / 最小可演示闭环**,再叠 **玩法细则、自动开奖调度、公告与站内信等展示类能力**。避免一上来就做全量 DDL 与全部后台菜单,导致联调面过大。
|
||||
|
||||
### 2.1 总览(与下文 P0~P4 对应)
|
||||
|
||||
| 阶段 | 目标 | 主要内容 |
|
||||
|------|------|----------|
|
||||
| **P0** | 数据地基 | 新建 `channel` 并适配 `game_user`、`admin`、`admin_group`;建立账本类最小表集(金额精度统一) |
|
||||
| **P0** | 数据地基 | 新建 `channel` 并适配 `user`、`admin`、`admin_group`;建立账本类最小表集(金额精度统一) |
|
||||
| **P1** | 游戏核心 | 期号、注单、开奖、系统配置;Redis 状态机 + MQ 异步落库 |
|
||||
| **P2** | 资金与风控 | 充提订单、流水账、提现审核、Jackpot / 大额拦截 |
|
||||
| **P3** | 代理与结算 | 流水占比分桶、级差、联营占成与负结转(表结构 + 跑批骨架) |
|
||||
| **P4** | 运营与审计 | 公告(含 Pop-out)、站内信、后台 RBAC 与操作审计 |
|
||||
|
||||
### 2.2 建议先做的:第一批建表(核心路径)
|
||||
|
||||
按依赖顺序执行 DDL(或 Phinx),**优先保证下列表/变更已落地**,其余表可分期补建:
|
||||
|
||||
| 顺序 | 类型 | 对象 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | 新建 | **`channel`** | 渠道与顶级代理参数;替代历史 `game_channel` 方向 |
|
||||
| 2 | 适配 | **`user`、`admin`、`admin_group`** | `coin` 精度、`channel_id`、邀请码与风控等(见 §四~§六) |
|
||||
| 3 | 新建 | **`game_config`** | 全局可调参数(局时长、下注窗口等),避免写死 |
|
||||
| 4 | 新建 | **`user_wallet_record`** | 玩家钱包流水,与余额变更同事务、可审计 |
|
||||
| 5 | 新建 | **`game_period`**、**`bet_order`** | 最小「一期号 + 注单」闭环;先有表再写状态机与接口 |
|
||||
|
||||
**第一批可暂缓的表(归入后续迭代)**:`game_bet_auto`(自动托管)、`operation_notice` / `user_notice_read` / `user_site_message`(纯展示与触达)、`game_agent_wallet` 及结算域各表(代理分佣与联营跑批)。
|
||||
|
||||
### 2.3 建议先做的:第一批后台菜单(与核心能力对齐)
|
||||
|
||||
在 BuildAdmin **`admin_rule`** 中,**优先保证**与下列能力相关的菜单与按钮权限可配置(具体 `component` 路径以仓库为准):
|
||||
|
||||
| 优先级 | 菜单能力 | 目的 |
|
||||
|--------|----------|------|
|
||||
| 高 | **渠道**、**游戏用户**、**角色组 / 管理员**(含渠道与邀请码相关字段) | 搭好「谁管哪条线、玩家归谁」 |
|
||||
| 高 | **用户管理 / 用户钱包流水** | 覆盖玩家主数据与账务对账 |
|
||||
| 高 | **游戏对局 / 压注订单** | 核心玩法与对账入口 |
|
||||
| 中 | **游戏配置 / 36字花字典** | 运营参数与字典维护 |
|
||||
| 中 | **充值订单 / 提现订单** | 充提流程管理与审核 |
|
||||
| 低 | **公告 / 站内信**、**代理结算** 等 | 属 **P3~P4**,核心跑通后再接 |
|
||||
|
||||
### 2.4 明确放到「细节阶段」的能力(勿阻塞第一期)
|
||||
|
||||
下列项依赖 **P1 状态机、定时任务、前端 C 端页面** 较完整后再做,避免与「账号 + 余额 + 最小下注」抢工期:
|
||||
|
||||
- **玩法细则**:选号上限、连胜倍率、Jackpot 条件等在 `game_config` 与代码中逐步细化即可。
|
||||
- **自动开奖 / 调度**:依赖期号服务、Redis 与(可选)MQ;表结构可先建,**调度与算票逻辑后迭代**。
|
||||
- **游戏公告、强弹窗、站内信展示**:表可后建;**后台编辑 + C 端展示** 属于运营体验增强,放在核心闭环之后。
|
||||
|
||||
### 2.5 最小可验收里程碑(建议)
|
||||
|
||||
1. **P0 完成**:能创建渠道与子代理管理员,玩家归属正确,`coin` 与 `user_wallet_record` 手工或脚本入账可对上。
|
||||
2. **P1 骨架完成**:能创建期号、写入注单、产生钱包流水(即使开奖仍为**手动或脚本模拟**)。
|
||||
3. 再推进 **自动流程、充提、公告展示** 与 **P3 结算**。
|
||||
|
||||
### 2.6 本次重构迁移顺序(已执行口径)
|
||||
|
||||
1. 新建订单表:`deposit_order`、`withdraw_order`(带 `order_no` 唯一索引、`user_id/status/create_time` 常用索引)。
|
||||
2. 硬重命名:`game_user -> user`、`game_bet_order -> bet_order`。
|
||||
3. 重建菜单与权限目录:`user`、`order`、`game`、`config`、`record`。
|
||||
4. 新增菜单节点:
|
||||
- `user/user`
|
||||
- `order/betOrder`、`order/depositOrder`、`order/withdrawOrder`
|
||||
- `game/period`
|
||||
- `config/gameConfig`、`config/ziHuaDictionary`
|
||||
- `record/userWalletRecord`
|
||||
5. 清理收口:删除迁移过程中的冗余权限别名规则,仅保留最终模块节点(`user/order/game/config/record`)。
|
||||
|
||||
---
|
||||
|
||||
## 三、`channel` 设计(替代 `game_channel`)
|
||||
@@ -54,7 +125,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 四、`game_user` 适配(用户表)
|
||||
## 四、`user` 适配(用户表)
|
||||
|
||||
### 4.1 建议保留字段
|
||||
|
||||
@@ -109,11 +180,13 @@
|
||||
|
||||
### 7.1 游戏引擎
|
||||
|
||||
**对局范围**:与 §1.1 一致——**全平台一套期号**,不按渠道分桌;详见 `game_period` 无 `channel_id` 的 DDL。
|
||||
|
||||
| 表名 | 用途 |
|
||||
|------|------|
|
||||
| `game_period`(或 `game_issue`) | 期号、状态机阶段、开奖号码、自动/手动、作废标记 |
|
||||
| `game_bet_order` | 注单:期号、用户、选号、单注额、总额、状态(扣款 / 结算 / 退款) |
|
||||
| `game_bet_auto`(可选) | 自动托管:剩余局数、选号快照、启停状态 |
|
||||
| `game_period`(或 `game_issue`) | **全局**期号、状态机阶段、开奖号码、自动/手动、作废标记(**非**每渠道一局) |
|
||||
| `bet_order` | 注单:关联全局 `period_id`;可选 `channel_id` 为用户归属快照(分润/查询),**非**独立牌桌 |
|
||||
| `game_bet_auto`(可选) | 自动托管:剩余局数、选号快照、启停状态(仍挂在全局期号下) |
|
||||
|
||||
### 7.2 系统配置
|
||||
|
||||
@@ -125,7 +198,7 @@
|
||||
|
||||
| 表名 | 用途 |
|
||||
|------|------|
|
||||
| `user_wallet_ledger`(或 `game_user_balance_log`) | 充值、下注扣款、派彩、提现冻结、手续费、人工调账;金额 **`decimal(18,4)`** |
|
||||
| **`user_wallet_record`**(原草案名 `user_wallet_ledger`,以 DDL 为准) | **玩家游戏币钱包流水**:充值、提现(含冻结/解冻)、平台划入划出、管理员加扣币、下注、派彩、手续费、作废退款、调账等;金额 **`decimal(18,4)`**,**只增不改**;与 `user.coin` 变更须同事务 + 条件更新 |
|
||||
| `deposit_order` | 第三方充值、法币金额、汇率、游戏币到账、回调状态 |
|
||||
| `withdraw_order` | 提现申请、手续费、审核状态、Jackpot / 大额标记 |
|
||||
|
||||
@@ -155,15 +228,27 @@
|
||||
|
||||
---
|
||||
|
||||
## 七、验证步骤
|
||||
## 八、验证步骤
|
||||
|
||||
### 7.1 数据库与模型
|
||||
### 8.1 数据库与模型
|
||||
|
||||
1. DDL 执行后检查:金额字段均为 **`decimal(18,4)`**;`channel`、`game_user`、`admin`、`admin_group`、期号等关联字段有合理索引。
|
||||
1. DDL 执行后检查:金额字段均为 **`decimal(18,4)`**;`channel`、`user`、`admin`、`admin_group`、期号等关联字段有合理索引。
|
||||
2. 创建测试渠道(顶级)→ 创建子代理 `admin`(设置 `parent_admin_id`、`channel_id`、`invite_code`)→ 创建用户并挂载 `channel_id`,链路与后台筛选正常。
|
||||
3. 扣款更新采用「**条件更新**」语义(如 `WHERE balance >= 扣款额`),压测或单测验证**不出现负余额**。
|
||||
|
||||
### 7.2 业务规则(对照 PRD / 业务流程)
|
||||
### 8.1.1 菜单与权限回归(本次新增)
|
||||
|
||||
1. 使用超管登录,确认五大目录 `user/order/game/config/record` 可见。
|
||||
2. 使用普通角色登录,逐项验证按钮权限(`index/add/edit/del/save`)是否与授权一致。
|
||||
3. 抽查重点页面:
|
||||
- `user/user`
|
||||
- `order/depositOrder`、`order/withdrawOrder`、`order/betOrder`
|
||||
- `game/period`
|
||||
- `config/gameConfig`、`config/ziHuaDictionary`
|
||||
- `record/userWalletRecord`
|
||||
4. 验证 `config/ziHuaDictionary` 的读取与保存权限均命中,且仅授权角色可操作。
|
||||
|
||||
### 8.2 业务规则(对照 PRD / 业务流程)
|
||||
|
||||
| 场景 | 验证要点 |
|
||||
|------|----------|
|
||||
@@ -175,14 +260,14 @@
|
||||
| 提现 | 有效投注 ≥ 总充值×配置倍数;最低提现与 0.5% 手续费;大额 / Jackpot 进审核 |
|
||||
| 灾难恢复 | 模拟期卡在「计算中」,启动退本流程:期作废、本金退回账本 |
|
||||
|
||||
### 7.3 代理与结算(逻辑就绪后)
|
||||
### 8.3 代理与结算(逻辑就绪后)
|
||||
|
||||
1. 构造多代理树与多用户下注,跑一期结算脚本或单元测试纯算法。
|
||||
2. **大盘亏损**:本期代理分佣为 0。
|
||||
3. **大盘盈利**:流水占比分桶 + 级差,各级金额之和与线总包一致(可对照文档数值验算)。
|
||||
4. **联营**:客损为负产生负结转;下期盈利先抵扣再分佣。
|
||||
|
||||
### 7.4 非功能
|
||||
### 8.4 非功能
|
||||
|
||||
- Redis:当前期注单池、倒计时、近 30 期开奖缓存。
|
||||
- MQ:派彩异步消费**幂等**(如 `period_id` + 用户 + 业务单号)。
|
||||
@@ -190,15 +275,16 @@
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与依赖
|
||||
## 九、风险与依赖
|
||||
|
||||
1. **子代理数据源**:子代理统一在 `admin`,避免在 `channel` 重复建树造成双主数据源。
|
||||
2. **现有 `profit_amount`(decimal(5,2))**:与游戏币 **18,4** 精度不一致,演进时改为 `decimal(18,4)` 或迁移至结算域,避免对账误差。
|
||||
3. 文档要求 **AI 算票在 Redis 内完成**,主库避免结算期同步锁表;账本与注单以异步一致性与幂等为准。
|
||||
4. **全局对局一致性**:任何需求(多租户展示、渠道后台)均不得引入「按渠道独立期号/独立开奖」;若出现产品歧义,以 **§1.1** 为准,避免公平性质疑与客诉。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关文档索引
|
||||
## 十、相关文档索引
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
@@ -208,15 +294,15 @@
|
||||
| `docs/CRUD生成逻辑说明.md` | BuildAdmin CRUD 路径与表名规范 |
|
||||
| `database/dfw_36zihua_schema_v1.sql` | 具体 DDL:字段、索引、注释 |
|
||||
|
||||
### 9.1 新建表一览(字段以 SQL 为准)
|
||||
### 10.1 新建表一览(字段以 SQL 为准)
|
||||
|
||||
| 序号 | 表名 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | `game_config` | 动态参数 KV |
|
||||
| 2 | `game_period` | 期号与状态机 |
|
||||
| 3 | `game_bet_order` | 注单 |
|
||||
| 3 | `bet_order` | 注单 |
|
||||
| 4 | `game_bet_auto` | 自动托管 |
|
||||
| 5 | `user_wallet_ledger` | 用户游戏币账本 |
|
||||
| 5 | `user_wallet_record` | 玩家游戏币钱包流水(替代旧名 `user_wallet_ledger`) |
|
||||
| 6 | `deposit_order` | 充值订单 |
|
||||
| 7 | `withdraw_order` | 提现订单 |
|
||||
| 8 | `game_agent_wallet` | 代理钱包 |
|
||||
@@ -229,7 +315,7 @@
|
||||
| 15 | `user_notice_read` | 公告已读/确认 |
|
||||
| 16 | `user_site_message` | 站内信 |
|
||||
|
||||
**既有表变更**:新建 `channel` 替代 `game_channel`;`admin` 增加子代理字段(`parent_admin_id`、`channel_id`、`invite_code`、`agent_role`)用于代理邀请链路管理;`admin_group` 增加 `channel_id` 实现角色组按渠道隔离;`game_user` 调整 `coin` 精度并增加 `channel_id`、`email`、`register_invite_code`、累计流水与风控、连胜兜底等字段。
|
||||
**既有表变更**:新建 `channel` 替代 `game_channel`;`admin` 增加子代理字段(`parent_admin_id`、`channel_id`、`invite_code`、`agent_role`)用于代理邀请链路管理;`admin_group` 增加 `channel_id` 实现角色组按渠道隔离;`game_user` 已硬重命名为 `user`,`game_bet_order` 已硬重命名为 `bet_order`;`user_wallet_record`、`game_config` 保持现名。
|
||||
|
||||
---
|
||||
|
||||
@@ -242,3 +328,102 @@
|
||||
| V1.2 | 2026-04-14 | 修正口径:`game_channel` 仅顶级分红参数,子代理迁移至 `admin` |
|
||||
| V1.3 | 2026-04-14 | 收敛权限:仅超管可开奖,渠道/子代理仅拉用户 |
|
||||
| V1.4 | 2026-04-14 | 新建 `channel` 替代 `game_channel`,`admin_group` 按渠道隔离 |
|
||||
| V1.5 | 2026-04-15 | 落地 `game_user_wallet_record` 玩家钱包流水表;文档增补字段说明附录;与 DDL 统一命名 |
|
||||
| V1.6 | 2026-04-15 | 增加「先核心后细节」实施顺序:先建表/菜单清单、可暂缓表与细节阶段能力;修正章节编号(验证步骤改为第八章) |
|
||||
| V1.7 | 2026-04-15 | 明确「多渠道、单场对局」:全平台共用 `game_period` 与同一开奖;渠道仅归属/分润;附录 `game_period`/`bet_order` 注释对齐 |
|
||||
| V1.8 | 2026-04-15 | §7.1 游戏引擎表与 §1.1 对齐:显式说明全局期号、`bet_order.channel_id` 仅为归属快照 |
|
||||
| V1.9 | 2026-04-15 | 落地模块化重构:`game_user->user`、`game_bet_order->bet_order`;新增 `deposit_order`/`withdraw_order`;后台菜单重组为 `user/order/game/config/record` 五大目录 |
|
||||
| V1.10 | 2026-04-15 | 完成表名与权限规则收口:`user_wallet_record`、`game_config` 保持不变;移除冗余 snake alias 菜单规则,文档口径与线上结构一致 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、附录:主要表字段说明(与 `database/dfw_36zihua_schema_v1.sql` 同步)
|
||||
|
||||
以下为各表**字段含义与使用要点**,便于评审与联调;**以仓库内 DDL 为最终口径**。
|
||||
|
||||
### 11.1 `channel`(渠道 / 顶级代理分红参数)
|
||||
|
||||
| 字段 | 作用 |
|
||||
|------|------|
|
||||
| `id` | 主键 |
|
||||
| `code` | 渠道标识,业务唯一 |
|
||||
| `invite_code` | 渠道侧邀请码(与 PRD「邀请码由管理员生成」并存时,以产品定义为准) |
|
||||
| `name` | 渠道名称 |
|
||||
| `agent_mode` | `turnover` 普通刷水 / `affiliate` 联营,决定分红与契约字段使用方式 |
|
||||
| `profit_amount` / `total_profit_amount` / `commission_pool_amount` | 经营与分红池快照类金额,**decimal(18,4)** |
|
||||
| `turnover_share_rate` / `affiliate_share_rate` / `affiliate_fee_rate` | 分红与联营费率类参数 |
|
||||
| `carryover_balance` | 联营负结转余额(可负) |
|
||||
| `top_admin_id` | 顶级代理管理员,关联 `admin.id` |
|
||||
| `admin_id` / `admin_group_id` | 创建人、渠道绑定的角色组 |
|
||||
| `status` / `remark` / `create_time` / `update_time` | 状态、备注、时间戳 |
|
||||
|
||||
### 11.2 `user`(C 端玩家)
|
||||
|
||||
| 字段 | 作用 |
|
||||
|------|------|
|
||||
| `coin` | 当前游戏币余额,**decimal(18,4)**,更新须条件更新防负余额 |
|
||||
| `channel_id` | 归属渠道;历史 `game_channel_id` 若仍存在,迁移期注意双写/对照 |
|
||||
| `register_invite_code` | 注册时邀请码快照,用于审计与归属 |
|
||||
| `total_deposit_coin` / `total_valid_bet_coin` | 累计充值入账、累计有效投注;提现流水倍数校验用 |
|
||||
| `risk_flags` | 风控位(如禁止登录/下注/提现,按位定义) |
|
||||
| `current_streak` / `last_bet_period_no` | 连胜与期号兜底;高频仍以 Redis 为准 |
|
||||
| `admin_id` | 归属子代理管理员 |
|
||||
| 其余 | 账号、头像、状态、时间戳等见 DDL |
|
||||
|
||||
### 11.3 `admin` / `admin_group`(子代理与角色组)
|
||||
|
||||
| 表/字段 | 作用 |
|
||||
|---------|------|
|
||||
| `admin.parent_admin_id` | 子代理上下级 |
|
||||
| `admin.channel_id` | 所属渠道 |
|
||||
| `admin.invite_code` | 子代理邀请码,注册归属 |
|
||||
| `admin.agent_role` | 角色类型(均无开奖权) |
|
||||
| `admin_group.channel_id` | 角色组归属渠道;`NULL` 可为系统级(仅超管) |
|
||||
| `admin_group.commission_rate` | 角色组分红比例(百分比) |
|
||||
|
||||
### 11.4 `game_config`(动态参数)
|
||||
|
||||
| 字段 | 作用 |
|
||||
|------|------|
|
||||
| `config_key` | 全局唯一键 |
|
||||
| `config_value` / `value_type` | 参数值及类型(含 JSON) |
|
||||
| `remark` | 说明,禁止业务写死应读此表 |
|
||||
|
||||
### 11.5 `game_period` / `bet_order` / `game_bet_auto`
|
||||
|
||||
| 表 | 要点 |
|
||||
|----|------|
|
||||
| `game_period` | **全平台唯一**期号:`period_no` 全局唯一;`status` 状态机;开奖结果与作废原因。**无 `channel_id` 字段**:对局不按渠道拆分。 |
|
||||
| `bet_order` | 注单关联**全局** `period_id`;`channel_id`(若有)为**用户/归属快照**,便于分润与查询,**不表示**独立牌桌或独立开奖。`idempotency_key` 幂等;金额 **18,4**;`win_amount` / `jackpot_extra_amount` |
|
||||
| `game_bet_auto` | 托管剩余局数、选号快照、抖动时间等;仍挂在**全局**期号下 |
|
||||
|
||||
### 11.6 `user_wallet_record`(玩家钱包流水)
|
||||
|
||||
| 字段 | 作用 |
|
||||
|------|------|
|
||||
| `user_id` | 玩家 `user.id` |
|
||||
| `channel_id` | **账务发生时**渠道快照,便于按渠道查询与对账;入账逻辑应写入 |
|
||||
| `biz_type` | 业务类型:`deposit`、`withdraw`、`platform_in`、`platform_out`、`admin_credit`、`admin_deduct`、`bet`、`payout`、`fee`、`void_refund`、`adjust` 等(字符串枚举,与代码常量一致) |
|
||||
| `direction` | `1` 入金 / `2` 出金;与 `amount` 恒正配合使用 |
|
||||
| `amount` | 变动额,**恒正** |
|
||||
| `balance_before` / `balance_after` | 变动前后余额,对账与审计 |
|
||||
| `ref_type` / `ref_id` | 关联订单或业务主键(如 `deposit_order`、`bet_order`) |
|
||||
| `idempotency_key` | 幂等键,防重复记账(唯一索引,允许多 `NULL`) |
|
||||
| `operator_admin_id` | 人工加扣币时的操作者 `admin.id` |
|
||||
| `remark` | 说明 |
|
||||
| `create_time` | 流水时间;**本表不设 `update_time`,禁止 UPDATE** |
|
||||
|
||||
### 11.7 `deposit_order` / `withdraw_order`
|
||||
|
||||
| 表 | 要点 |
|
||||
|----|------|
|
||||
| `deposit_order` | 法币、`fx_rate`、`coin_amount`、网关单号、回调存档 |
|
||||
| `withdraw_order` | 申请额、手续费、审核与 Jackpot 标记、驳回原因 |
|
||||
|
||||
### 11.8 代理钱包与结算域(`game_agent_wallet`、`game_agent_wallet_ledger`、`agent_settlement_period`、`agent_commission_record`、`affiliate_*`)
|
||||
|
||||
见 DDL 注释:代理余额与流水、结算周期大盘快照、佣金行、联营契约与负结转;金额均为 **decimal(18,4)**,与玩家账本区分职责。
|
||||
|
||||
### 11.9 运营与消息(`operation_notice`、`user_notice_read`、`user_site_message`)
|
||||
|
||||
公告类型(含强弹)、已读确认;站内信与公告分离存储,详见各表 `COMMENT`。
|
||||
|
||||
19
web/src/lang/backend/en/agent/commissionRecord.ts
Normal file
19
web/src/lang/backend/en/agent/commissionRecord.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID/Settlement period ID/Remark',
|
||||
id: 'ID',
|
||||
settlement_period_id: 'Settlement period ID',
|
||||
channel_id: 'Channel ID',
|
||||
admin_id: 'Agent admin ID',
|
||||
commission_rate: 'Commission rate',
|
||||
calc_base_amount: 'Calculation base amount',
|
||||
commission_amount: 'Commission amount',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending',
|
||||
'status 1': 'Paid',
|
||||
'status 2': 'Reverted',
|
||||
settled_at: 'Settled at',
|
||||
remark: 'Remark',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
}
|
||||
|
||||
19
web/src/lang/backend/en/agent/settlementPeriod.ts
Normal file
19
web/src/lang/backend/en/agent/settlementPeriod.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID/Settlement No./Remark',
|
||||
id: 'ID',
|
||||
settlement_no: 'Settlement No.',
|
||||
period_start_at: 'Period start',
|
||||
period_end_at: 'Period end',
|
||||
total_bet_amount: 'Total bet amount',
|
||||
total_payout_amount: 'Total payout amount',
|
||||
platform_profit_amount: 'Platform profit',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending',
|
||||
'status 1': 'Processing',
|
||||
'status 2': 'Completed',
|
||||
'status 3': 'Closed',
|
||||
remark: 'Remark',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
}
|
||||
|
||||
3
web/src/lang/backend/en/config/gameConfig.ts
Normal file
3
web/src/lang/backend/en/config/gameConfig.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import gameConfig from '../game/config'
|
||||
export default gameConfig
|
||||
|
||||
3
web/src/lang/backend/en/config/ziHuaDictionary.ts
Normal file
3
web/src/lang/backend/en/config/ziHuaDictionary.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import dict from '../game/ziHuaDictionary'
|
||||
export default dict
|
||||
|
||||
@@ -22,14 +22,8 @@ export default {
|
||||
idempotency_key: 'Idempotency key',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
gamePeriod: {
|
||||
period_no: 'Period (relation)',
|
||||
status: 'Period status',
|
||||
},
|
||||
gameUser: {
|
||||
username: 'Username',
|
||||
},
|
||||
channel: {
|
||||
name: 'Channel',
|
||||
},
|
||||
gamePeriod_period_no: 'Period (relation)',
|
||||
gamePeriod_status: 'Period status',
|
||||
user_username: 'Username',
|
||||
channel_name: 'Channel',
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
id: 'Record ID',
|
||||
user_id: 'User ID',
|
||||
'game_user__username': 'Username',
|
||||
'user__username': 'Username',
|
||||
'channel__name': 'Channel',
|
||||
biz_type: 'Biz type',
|
||||
direction: 'Direction',
|
||||
|
||||
29
web/src/lang/backend/en/order/betOrder.ts
Normal file
29
web/src/lang/backend/en/order/betOrder.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID / Period / Idempotency',
|
||||
id: 'ID',
|
||||
period_id: 'Period ID',
|
||||
period_no: 'Period No.',
|
||||
user_id: 'User ID',
|
||||
channel_id: 'Channel ID',
|
||||
pick_numbers: 'Picks',
|
||||
unit_amount: 'Unit amount',
|
||||
pick_count: 'Pick count',
|
||||
total_amount: 'Total',
|
||||
streak_at_bet: 'Streak at bet',
|
||||
is_auto: 'Auto',
|
||||
'is_auto 0': 'Manual',
|
||||
'is_auto 1': 'Auto bet',
|
||||
win_amount: 'Payout',
|
||||
jackpot_extra_amount: 'Jackpot extra',
|
||||
status: 'Status',
|
||||
'status 1': 'Pending draw',
|
||||
'status 2': 'Settled',
|
||||
'status 3': 'Refunded',
|
||||
idempotency_key: 'Idempotency key',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
gamePeriod_period_no: 'Period (relation)',
|
||||
gamePeriod_status: 'Period status',
|
||||
user_username: 'Username',
|
||||
channel_name: 'Channel',
|
||||
}
|
||||
20
web/src/lang/backend/en/order/depositOrder.ts
Normal file
20
web/src/lang/backend/en/order/depositOrder.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
'quick Search Fields': 'Order No./User ID/Pay channel',
|
||||
id: 'ID',
|
||||
order_no: 'Order No.',
|
||||
user_id: 'User ID',
|
||||
channel_id: 'Channel ID',
|
||||
amount: 'Amount',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending',
|
||||
'status 1': 'Success',
|
||||
'status 2': 'Failed',
|
||||
'status 3': 'Canceled',
|
||||
pay_channel: 'Pay channel',
|
||||
pay_time: 'Pay time',
|
||||
remark: 'Remark',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
user_username: 'Username',
|
||||
channel_name: 'Channel',
|
||||
}
|
||||
23
web/src/lang/backend/en/order/withdrawOrder.ts
Normal file
23
web/src/lang/backend/en/order/withdrawOrder.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
'quick Search Fields': 'Order No./User ID',
|
||||
id: 'ID',
|
||||
order_no: 'Order No.',
|
||||
user_id: 'User ID',
|
||||
channel_id: 'Channel ID',
|
||||
amount: 'Apply amount',
|
||||
fee: 'Fee',
|
||||
actual_amount: 'Actual amount',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending review',
|
||||
'status 1': 'Approved',
|
||||
'status 2': 'Rejected',
|
||||
'status 3': 'Paid',
|
||||
review_admin_id: 'Reviewer',
|
||||
review_time: 'Review time',
|
||||
remark: 'Remark',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
user_username: 'Username',
|
||||
channel_name: 'Channel',
|
||||
review_admin_username: 'Reviewer',
|
||||
}
|
||||
33
web/src/lang/backend/en/record/userWalletRecord.ts
Normal file
33
web/src/lang/backend/en/record/userWalletRecord.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
id: 'Record ID',
|
||||
user_id: 'User ID',
|
||||
'user__username': 'Username',
|
||||
'channel__name': 'Channel',
|
||||
biz_type: 'Biz type',
|
||||
direction: 'Direction',
|
||||
amount: 'Amount',
|
||||
balance_before: 'Balance before',
|
||||
balance_after: 'Balance after',
|
||||
ref_type: 'Ref type',
|
||||
ref_id: 'Ref ID',
|
||||
idempotency_key: 'Idempotency key',
|
||||
'operator_admin__username': 'Operator',
|
||||
remark: 'Remark',
|
||||
create_time: 'Created at',
|
||||
'quick Search Fields': 'ID, biz type, ref type, remark, idempotency key',
|
||||
'direction in': 'Credit',
|
||||
'direction out': 'Debit',
|
||||
'biz deposit': 'Deposit',
|
||||
'biz withdraw': 'Withdraw',
|
||||
'biz withdraw_freeze': 'Withdraw freeze',
|
||||
'biz withdraw_unfreeze': 'Withdraw unfreeze',
|
||||
'biz platform_in': 'Platform credit',
|
||||
'biz platform_out': 'Platform debit',
|
||||
'biz admin_credit': 'Admin credit',
|
||||
'biz admin_deduct': 'Admin debit',
|
||||
'biz bet': 'Bet',
|
||||
'biz payout': 'Payout',
|
||||
'biz fee': 'Fee',
|
||||
'biz void_refund': 'Void refund',
|
||||
'biz adjust': 'Adjustment',
|
||||
}
|
||||
45
web/src/lang/backend/en/user/user.ts
Normal file
45
web/src/lang/backend/en/user/user.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
id: 'ID',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
uuid: 'UUID',
|
||||
phone: 'Phone',
|
||||
email: 'Email',
|
||||
email_placeholder: 'Optional (register with phone or email)',
|
||||
head_image: 'Avatar',
|
||||
remark: 'Remark',
|
||||
coin: 'Coin balance',
|
||||
coin_placeholder: 'decimal(18,4)',
|
||||
total_deposit_coin: 'Total deposit (coin)',
|
||||
total_valid_bet_coin: 'Total valid bet (coin)',
|
||||
risk_flags: 'Risk',
|
||||
risk_none: 'None',
|
||||
risk_no_login: 'No login',
|
||||
risk_no_bet: 'No bet',
|
||||
risk_no_withdraw: 'No withdraw',
|
||||
current_streak: 'Win streak',
|
||||
last_bet_period_no: 'Last bet period',
|
||||
last_bet_period_no_placeholder: 'DB fallback for streak',
|
||||
register_invite_code: 'Invite code (snapshot)',
|
||||
register_invite_code_placeholder: 'Invite code at registration',
|
||||
status: 'Status',
|
||||
'status 0': 'Disabled',
|
||||
'status 1': 'Enabled',
|
||||
section_admin_attribution: 'Administrator',
|
||||
admin_affiliation: 'Assigned admin',
|
||||
admin_affiliation_placeholder: 'Role group tree — only admins in your scope',
|
||||
register_invite_code_auto_placeholder: 'Filled from selected admin invite code',
|
||||
channel_id: 'Channel',
|
||||
channel__name: 'Channel',
|
||||
admin_id: 'Admin',
|
||||
admin__username: 'Admin',
|
||||
create_time: 'Created',
|
||||
update_time: 'Updated',
|
||||
'quick Search Fields': 'ID, username, phone, email, invite code',
|
||||
section_basic: 'Account',
|
||||
section_register: 'Registration',
|
||||
section_finance: 'Balance & turnover',
|
||||
section_risk: 'Risk control',
|
||||
section_streak: 'Streak (fallback)',
|
||||
section_other: 'Other',
|
||||
}
|
||||
19
web/src/lang/backend/zh-cn/agent/commissionRecord.ts
Normal file
19
web/src/lang/backend/zh-cn/agent/commissionRecord.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID/结算周期ID/备注',
|
||||
id: 'ID',
|
||||
settlement_period_id: '结算周期ID',
|
||||
channel_id: '渠道ID',
|
||||
admin_id: '代理管理员ID',
|
||||
commission_rate: '佣金比例',
|
||||
calc_base_amount: '结算基数',
|
||||
commission_amount: '佣金金额',
|
||||
status: '状态',
|
||||
'status 0': '待发放',
|
||||
'status 1': '已发放',
|
||||
'status 2': '已回退',
|
||||
settled_at: '发放时间',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
}
|
||||
|
||||
19
web/src/lang/backend/zh-cn/agent/settlementPeriod.ts
Normal file
19
web/src/lang/backend/zh-cn/agent/settlementPeriod.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID/结算周期号/备注',
|
||||
id: 'ID',
|
||||
settlement_no: '结算周期号',
|
||||
period_start_at: '周期开始',
|
||||
period_end_at: '周期结束',
|
||||
total_bet_amount: '总投注额',
|
||||
total_payout_amount: '总派彩额',
|
||||
platform_profit_amount: '平台盈亏',
|
||||
status: '状态',
|
||||
'status 0': '待结算',
|
||||
'status 1': '结算中',
|
||||
'status 2': '已完成',
|
||||
'status 3': '已关闭',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
}
|
||||
|
||||
3
web/src/lang/backend/zh-cn/config/gameConfig.ts
Normal file
3
web/src/lang/backend/zh-cn/config/gameConfig.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import gameConfig from '../game/config'
|
||||
export default gameConfig
|
||||
|
||||
3
web/src/lang/backend/zh-cn/config/ziHuaDictionary.ts
Normal file
3
web/src/lang/backend/zh-cn/config/ziHuaDictionary.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import dict from '../game/ziHuaDictionary'
|
||||
export default dict
|
||||
|
||||
@@ -22,15 +22,8 @@ export default {
|
||||
idempotency_key: '幂等键',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
/** 关联展示列(须嵌套,供 t('game.betOrder.gamePeriod.xxx')) */
|
||||
gamePeriod: {
|
||||
period_no: '对局期号',
|
||||
status: '期状态',
|
||||
},
|
||||
gameUser: {
|
||||
username: '用户名',
|
||||
},
|
||||
channel: {
|
||||
name: '渠道',
|
||||
},
|
||||
gamePeriod_period_no: '对局期号',
|
||||
gamePeriod_status: '期状态',
|
||||
user_username: '用户名',
|
||||
channel_name: '渠道',
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
id: '流水ID',
|
||||
user_id: '用户ID',
|
||||
'game_user__username': '用户名',
|
||||
'user__username': '用户名',
|
||||
'channel__name': '渠道',
|
||||
biz_type: '业务类型',
|
||||
direction: '方向',
|
||||
|
||||
29
web/src/lang/backend/zh-cn/order/betOrder.ts
Normal file
29
web/src/lang/backend/zh-cn/order/betOrder.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
'quick Search Fields': 'ID/期号/幂等键',
|
||||
id: 'ID',
|
||||
period_id: '期ID',
|
||||
period_no: '期号',
|
||||
user_id: '用户ID',
|
||||
channel_id: '渠道ID',
|
||||
pick_numbers: '选号',
|
||||
unit_amount: '单号金额',
|
||||
pick_count: '选号个数',
|
||||
total_amount: '总金额',
|
||||
streak_at_bet: '下注时连胜',
|
||||
is_auto: '托管',
|
||||
'is_auto 0': '手动',
|
||||
'is_auto 1': '托管',
|
||||
win_amount: '派彩',
|
||||
jackpot_extra_amount: 'Jackpot',
|
||||
status: '状态',
|
||||
'status 1': '待开奖',
|
||||
'status 2': '已结算',
|
||||
'status 3': '已退款',
|
||||
idempotency_key: '幂等键',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
gamePeriod_period_no: '对局期号',
|
||||
gamePeriod_status: '期状态',
|
||||
user_username: '用户名',
|
||||
channel_name: '渠道',
|
||||
}
|
||||
20
web/src/lang/backend/zh-cn/order/depositOrder.ts
Normal file
20
web/src/lang/backend/zh-cn/order/depositOrder.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
'quick Search Fields': '订单号/用户ID/支付通道',
|
||||
id: 'ID',
|
||||
order_no: '订单号',
|
||||
user_id: '用户ID',
|
||||
user_username: '用户名',
|
||||
channel_id: '渠道ID',
|
||||
channel_name: '渠道',
|
||||
amount: '金额',
|
||||
status: '状态',
|
||||
'status 0': '待处理',
|
||||
'status 1': '成功',
|
||||
'status 2': '失败',
|
||||
'status 3': '已取消',
|
||||
pay_channel: '支付通道',
|
||||
pay_time: '支付时间',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
}
|
||||
23
web/src/lang/backend/zh-cn/order/withdrawOrder.ts
Normal file
23
web/src/lang/backend/zh-cn/order/withdrawOrder.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
'quick Search Fields': '订单号/用户ID',
|
||||
id: 'ID',
|
||||
order_no: '订单号',
|
||||
user_id: '用户ID',
|
||||
channel_id: '渠道ID',
|
||||
amount: '申请金额',
|
||||
fee: '手续费',
|
||||
actual_amount: '实际到账',
|
||||
status: '状态',
|
||||
'status 0': '待审核',
|
||||
'status 1': '已通过',
|
||||
'status 2': '已拒绝',
|
||||
'status 3': '已打款',
|
||||
review_admin_id: '审核管理员',
|
||||
review_time: '审核时间',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
update_time: '更新时间',
|
||||
user_username: '用户名',
|
||||
channel_name: '渠道',
|
||||
review_admin_username: '审核人',
|
||||
}
|
||||
33
web/src/lang/backend/zh-cn/record/userWalletRecord.ts
Normal file
33
web/src/lang/backend/zh-cn/record/userWalletRecord.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
id: '流水ID',
|
||||
user_id: '用户ID',
|
||||
'user__username': '用户名',
|
||||
'channel__name': '渠道',
|
||||
biz_type: '业务类型',
|
||||
direction: '方向',
|
||||
amount: '变动额',
|
||||
balance_before: '变动前余额',
|
||||
balance_after: '变动后余额',
|
||||
ref_type: '关联类型',
|
||||
ref_id: '关联ID',
|
||||
idempotency_key: '幂等键',
|
||||
'operator_admin__username': '操作管理员',
|
||||
remark: '备注',
|
||||
create_time: '创建时间',
|
||||
'quick Search Fields': 'ID、业务类型、关联类型、备注、幂等键',
|
||||
'direction in': '入金',
|
||||
'direction out': '出金',
|
||||
'biz deposit': '充值入账',
|
||||
'biz withdraw': '提现出账',
|
||||
'biz withdraw_freeze': '提现冻结',
|
||||
'biz withdraw_unfreeze': '提现解冻',
|
||||
'biz platform_in': '平台划入',
|
||||
'biz platform_out': '平台划出',
|
||||
'biz admin_credit': '管理员加币',
|
||||
'biz admin_deduct': '管理员扣币',
|
||||
'biz bet': '下注扣款',
|
||||
'biz payout': '派彩',
|
||||
'biz fee': '手续费',
|
||||
'biz void_refund': '作废退款',
|
||||
'biz adjust': '其他调账',
|
||||
}
|
||||
45
web/src/lang/backend/zh-cn/user/user.ts
Normal file
45
web/src/lang/backend/zh-cn/user/user.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
id: 'ID',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
uuid: '用户唯一标识',
|
||||
phone: '手机号',
|
||||
email: '邮箱',
|
||||
email_placeholder: '可选,与手机号二选一注册时填写',
|
||||
head_image: '头像',
|
||||
remark: '备注',
|
||||
coin: '游戏币余额',
|
||||
coin_placeholder: 'decimal(18,4),禁止业务用浮点存库',
|
||||
total_deposit_coin: '累计充值(币)',
|
||||
total_valid_bet_coin: '累计有效投注(币)',
|
||||
risk_flags: '风控',
|
||||
risk_none: '无限制',
|
||||
risk_no_login: '禁止登录',
|
||||
risk_no_bet: '禁止下注',
|
||||
risk_no_withdraw: '禁止提现',
|
||||
current_streak: '当前连胜',
|
||||
last_bet_period_no: '最近下注期号',
|
||||
last_bet_period_no_placeholder: '连胜兜底同步用,可与 Redis 对照',
|
||||
register_invite_code: '注册邀请码快照',
|
||||
register_invite_code_placeholder: '注册时绑定渠道/代理邀请码',
|
||||
status: '状态',
|
||||
'status 0': '禁用',
|
||||
'status 1': '启用',
|
||||
section_admin_attribution: '管理员归属',
|
||||
admin_affiliation: '归属管理员',
|
||||
admin_affiliation_placeholder: '按角色组展开,仅展示您可管理范围内的管理员',
|
||||
register_invite_code_auto_placeholder: '随所选管理员邀请码自动带出',
|
||||
channel_id: '所属渠道',
|
||||
channel__name: '渠道名',
|
||||
admin_id: '归属管理员',
|
||||
admin__username: '管理员',
|
||||
create_time: '创建时间',
|
||||
update_time: '修改时间',
|
||||
'quick Search Fields': 'ID、用户名、手机号、邮箱、邀请码',
|
||||
section_basic: '账号信息',
|
||||
section_register: '注册与邀请',
|
||||
section_finance: '资金与流水',
|
||||
section_risk: '风控',
|
||||
section_streak: '连胜(兜底)',
|
||||
section_other: '其他',
|
||||
}
|
||||
@@ -37,6 +37,12 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
// 按需动态加载页面的语言包-start
|
||||
let loadPath: string[] = []
|
||||
const toCamelPath = (path: string): string => {
|
||||
return path
|
||||
.split('/')
|
||||
.map((seg) => seg.replace(/_([a-z])/g, (_m, c: string) => c.toUpperCase()))
|
||||
.join('/')
|
||||
}
|
||||
const config = useConfig()
|
||||
if (to.path in langAutoLoadMap) {
|
||||
loadPath.push(...langAutoLoadMap[to.path as keyof typeof langAutoLoadMap])
|
||||
@@ -47,7 +53,10 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
// 去除 path 中的 /admin
|
||||
const adminPath = to.path.slice(to.path.indexOf(adminBaseRoutePath) + adminBaseRoutePath.length)
|
||||
if (adminPath) loadPath.push(prefix + adminPath + '.ts')
|
||||
if (adminPath) {
|
||||
loadPath.push(prefix + adminPath + '.ts')
|
||||
loadPath.push(prefix + toCamelPath(adminPath) + '.ts')
|
||||
}
|
||||
} else {
|
||||
prefix = './frontend/' + config.lang.defaultLang
|
||||
loadPath.push(prefix + to.path + '.ts')
|
||||
@@ -55,7 +64,9 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
// 根据路由 name 加载的语言包
|
||||
if (to.name) {
|
||||
loadPath.push(prefix + '/' + to.name.toString() + '.ts')
|
||||
const routeName = to.name.toString()
|
||||
loadPath.push(prefix + '/' + routeName + '.ts')
|
||||
loadPath.push(prefix + '/' + toCamelPath(routeName) + '.ts')
|
||||
}
|
||||
|
||||
if (!window.loadLangHandle.publicMessageLoaded) window.loadLangHandle.publicMessageLoaded = []
|
||||
|
||||
80
web/src/views/backend/agent/commissionRecord/index.vue
Normal file
80
web/src/views/backend/agent/commissionRecord/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('agent.commissionRecord.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'agent/commissionRecord',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/agent.CommissionRecord/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('agent.commissionRecord.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('agent.commissionRecord.settlement_period_id'), prop: 'settlement_period_id', align: 'center', width: 130, operator: 'RANGE' },
|
||||
{ label: t('agent.commissionRecord.channel_id'), prop: 'channel_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{ label: t('agent.commissionRecord.admin_id'), prop: 'admin_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{ label: t('agent.commissionRecord.commission_rate'), prop: 'commission_rate', align: 'center', minWidth: 110, operator: 'RANGE' },
|
||||
{ label: t('agent.commissionRecord.calc_base_amount'), prop: 'calc_base_amount', align: 'center', minWidth: 120, operator: 'RANGE' },
|
||||
{ label: t('agent.commissionRecord.commission_amount'), prop: 'commission_amount', align: 'center', minWidth: 120, operator: 'RANGE' },
|
||||
{
|
||||
label: t('agent.commissionRecord.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: { '0': t('agent.commissionRecord.status 0'), '1': t('agent.commissionRecord.status 1'), '2': t('agent.commissionRecord.status 2') },
|
||||
},
|
||||
{ label: t('agent.commissionRecord.settled_at'), prop: 'settled_at', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('agent.commissionRecord.remark'), prop: 'remark', align: 'center', minWidth: 160, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), showOverflowTooltip: true },
|
||||
{ label: t('agent.commissionRecord.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('agent.commissionRecord.update_time'), prop: 'update_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('Operate'), align: 'center', width: 100, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: 0, commission_rate: '0.0000' },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
49
web/src/views/backend/agent/commissionRecord/popupForm.vue
Normal file
49
web/src/views/backend/agent/commissionRecord/popupForm.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="['Add', 'Edit'].includes(baTable.form.operate!)" @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 v-loading="baTable.form.loading" 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" @submit.prevent="" @keyup.enter="baTable.onSubmit(formRef)" :model="baTable.form.items" :label-position="config.layout.shrink ? 'top' : 'right'" :label-width="baTable.form.labelWidth + 'px'" :rules="rules">
|
||||
<FormItem :label="t('agent.commissionRecord.settlement_period_id')" type="number" v-model="baTable.form.items!.settlement_period_id" prop="settlement_period_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.channel_id')" type="number" v-model="baTable.form.items!.channel_id" prop="channel_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.admin_id')" type="number" v-model="baTable.form.items!.admin_id" prop="admin_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.commission_rate')" type="number" v-model="baTable.form.items!.commission_rate" prop="commission_rate" :input-attr="{ min: 0, precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.calc_base_amount')" type="number" v-model="baTable.form.items!.calc_base_amount" prop="calc_base_amount" :input-attr="{ min: 0, precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.commission_amount')" type="number" v-model="baTable.form.items!.commission_amount" prop="commission_amount" :input-attr="{ precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.commissionRecord.status')" type="radio" v-model="baTable.form.items!.status" prop="status" :input-attr="{ content: { '0': t('agent.commissionRecord.status 0'), '1': t('agent.commissionRecord.status 1'), '2': t('agent.commissionRecord.status 2') } }" />
|
||||
<FormItem :label="t('agent.commissionRecord.settled_at')" type="datetime" v-model="baTable.form.items!.settled_at" prop="settled_at" />
|
||||
<FormItem :label="t('agent.commissionRecord.remark')" type="textarea" v-model="baTable.form.items!.remark" prop="remark" :input-attr="{ rows: 2 }" />
|
||||
</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" @click="baTable.onSubmit(formRef)" type="primary">{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
settlement_period_id: [{ required: true, message: t('Please input field', { field: t('agent.commissionRecord.settlement_period_id') }) }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
79
web/src/views/backend/agent/settlementPeriod/index.vue
Normal file
79
web/src/views/backend/agent/settlementPeriod/index.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('agent.settlementPeriod.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'agent/settlementPeriod',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/agent.SettlementPeriod/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('agent.settlementPeriod.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('agent.settlementPeriod.settlement_no'), prop: 'settlement_no', align: 'center', minWidth: 160, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||
{ label: t('agent.settlementPeriod.period_start_at'), prop: 'period_start_at', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('agent.settlementPeriod.period_end_at'), prop: 'period_end_at', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('agent.settlementPeriod.total_bet_amount'), prop: 'total_bet_amount', align: 'center', operator: 'RANGE', minWidth: 120 },
|
||||
{ label: t('agent.settlementPeriod.total_payout_amount'), prop: 'total_payout_amount', align: 'center', operator: 'RANGE', minWidth: 120 },
|
||||
{ label: t('agent.settlementPeriod.platform_profit_amount'), prop: 'platform_profit_amount', align: 'center', operator: 'RANGE', minWidth: 120 },
|
||||
{
|
||||
label: t('agent.settlementPeriod.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: { '0': t('agent.settlementPeriod.status 0'), '1': t('agent.settlementPeriod.status 1'), '2': t('agent.settlementPeriod.status 2'), '3': t('agent.settlementPeriod.status 3') },
|
||||
},
|
||||
{ label: t('agent.settlementPeriod.remark'), prop: 'remark', align: 'center', minWidth: 160, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), showOverflowTooltip: true },
|
||||
{ label: t('agent.settlementPeriod.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('agent.settlementPeriod.update_time'), prop: 'update_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', width: 170, sortable: 'custom', timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('Operate'), align: 'center', width: 100, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: 0 },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
48
web/src/views/backend/agent/settlementPeriod/popupForm.vue
Normal file
48
web/src/views/backend/agent/settlementPeriod/popupForm.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="['Add', 'Edit'].includes(baTable.form.operate!)" @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 v-loading="baTable.form.loading" 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" @submit.prevent="" @keyup.enter="baTable.onSubmit(formRef)" :model="baTable.form.items" :label-position="config.layout.shrink ? 'top' : 'right'" :label-width="baTable.form.labelWidth + 'px'" :rules="rules">
|
||||
<FormItem :label="t('agent.settlementPeriod.settlement_no')" type="string" v-model="baTable.form.items!.settlement_no" prop="settlement_no" />
|
||||
<FormItem :label="t('agent.settlementPeriod.period_start_at')" type="datetime" v-model="baTable.form.items!.period_start_at" prop="period_start_at" />
|
||||
<FormItem :label="t('agent.settlementPeriod.period_end_at')" type="datetime" v-model="baTable.form.items!.period_end_at" prop="period_end_at" />
|
||||
<FormItem :label="t('agent.settlementPeriod.total_bet_amount')" type="number" v-model="baTable.form.items!.total_bet_amount" prop="total_bet_amount" :input-attr="{ min: 0, precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.settlementPeriod.total_payout_amount')" type="number" v-model="baTable.form.items!.total_payout_amount" prop="total_payout_amount" :input-attr="{ min: 0, precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.settlementPeriod.platform_profit_amount')" type="number" v-model="baTable.form.items!.platform_profit_amount" prop="platform_profit_amount" :input-attr="{ precision: 4, step: 0.0001 }" />
|
||||
<FormItem :label="t('agent.settlementPeriod.status')" type="radio" v-model="baTable.form.items!.status" prop="status" :input-attr="{ content: { '0': t('agent.settlementPeriod.status 0'), '1': t('agent.settlementPeriod.status 1'), '2': t('agent.settlementPeriod.status 2'), '3': t('agent.settlementPeriod.status 3') } }" />
|
||||
<FormItem :label="t('agent.settlementPeriod.remark')" type="textarea" v-model="baTable.form.items!.remark" prop="remark" :input-attr="{ rows: 2 }" />
|
||||
</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" @click="baTable.onSubmit(formRef)" type="primary">{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
settlement_no: [{ required: true, message: t('Please input field', { field: t('agent.settlementPeriod.settlement_no') }) }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
127
web/src/views/backend/config/gameConfig/index.vue
Normal file
127
web/src/views/backend/config/gameConfig/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('config.gameConfig.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'config/gameConfig',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/config.GameConfig/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('config.gameConfig.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('config.gameConfig.config_key'),
|
||||
prop: 'config_key',
|
||||
align: 'center',
|
||||
minWidth: 200,
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
operator: 'LIKE',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.gameConfig.config_value'),
|
||||
prop: 'config_value',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
operator: 'LIKE',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.gameConfig.value_type'),
|
||||
prop: 'value_type',
|
||||
align: 'center',
|
||||
width: 110,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: {
|
||||
string: t('config.gameConfig.value_type string'),
|
||||
int: t('config.gameConfig.value_type int'),
|
||||
decimal: t('config.gameConfig.value_type decimal'),
|
||||
json: t('config.gameConfig.value_type json'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('config.gameConfig.remark'),
|
||||
prop: 'remark',
|
||||
align: 'center',
|
||||
minWidth: 200,
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
operator: 'LIKE',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('config.gameConfig.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('config.gameConfig.update_time'),
|
||||
prop: 'update_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{ label: t('Operate'), align: 'center', width: 100, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
dblClickNotEditColumn: [undefined],
|
||||
},
|
||||
{
|
||||
defaultItems: { value_type: 'string', remark: '' },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
107
web/src/views/backend/config/gameConfig/popupForm.vue
Normal file
107
web/src/views/backend/config/gameConfig/popupForm.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="ba-operate-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="['Add', 'Edit'].includes(baTable.form.operate!)"
|
||||
@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 v-loading="baTable.form.loading" 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"
|
||||
@submit.prevent=""
|
||||
@keyup.enter="baTable.onSubmit(formRef)"
|
||||
:model="baTable.form.items"
|
||||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||||
:label-width="baTable.form.labelWidth + 'px'"
|
||||
:rules="rules"
|
||||
>
|
||||
<FormItem
|
||||
:label="t('config.gameConfig.config_key')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.config_key"
|
||||
prop="config_key"
|
||||
:placeholder="t('Please input field', { field: t('config.gameConfig.config_key') })"
|
||||
:input-attr="{ disabled: baTable.form.operate === 'Edit' }"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('config.gameConfig.config_value')"
|
||||
type="textarea"
|
||||
v-model="baTable.form.items!.config_value"
|
||||
prop="config_value"
|
||||
:input-attr="{ rows: 5 }"
|
||||
:placeholder="t('Please input field', { field: t('config.gameConfig.config_value') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('config.gameConfig.value_type')"
|
||||
type="radio"
|
||||
v-model="baTable.form.items!.value_type"
|
||||
prop="value_type"
|
||||
:input-attr="{
|
||||
content: {
|
||||
string: t('config.gameConfig.value_type string'),
|
||||
int: t('config.gameConfig.value_type int'),
|
||||
decimal: t('config.gameConfig.value_type decimal'),
|
||||
json: t('config.gameConfig.value_type json'),
|
||||
},
|
||||
}"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('config.gameConfig.remark')"
|
||||
type="textarea"
|
||||
v-model="baTable.form.items!.remark"
|
||||
prop="remark"
|
||||
:input-attr="{ rows: 2 }"
|
||||
/>
|
||||
</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" @click="baTable.onSubmit(formRef)" type="primary">
|
||||
{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
config_key: [buildRequired('config.gameConfig.config_key')],
|
||||
value_type: [buildRequired('config.gameConfig.value_type')],
|
||||
})
|
||||
|
||||
function buildRequired(langKey: string): FormItemRule {
|
||||
return {
|
||||
required: true,
|
||||
message: t('Please input field', { field: t(langKey) }),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
104
web/src/views/backend/config/ziHuaDictionary/index.vue
Normal file
104
web/src/views/backend/config/ziHuaDictionary/index.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box zi-hua-dict-page">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
{{ t('config.ziHuaDictionary.desc') }}
|
||||
</el-alert>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" :loading="saving" :disabled="loading" @click="onSave">
|
||||
{{ t('config.ziHuaDictionary.btn_save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" border stripe :data="items" row-key="no" max-height="640">
|
||||
<el-table-column prop="no" :label="t('config.ziHuaDictionary.no')" width="72" align="center" />
|
||||
<el-table-column :label="t('config.ziHuaDictionary.name')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name" maxlength="32" show-word-limit />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('config.ziHuaDictionary.category')" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.category" style="width: 100%">
|
||||
<el-option v-for="c in categories" :key="c" :label="categoryLabel(c)" :value="c" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import createAxios from '/@/utils/axios'
|
||||
import { auth } from '/@/utils/common'
|
||||
|
||||
defineOptions({
|
||||
name: 'config/ziHuaDictionary',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
type Item = { no: number; name: string; category: string }
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const items = ref<Item[]>([])
|
||||
const categories = ref<string[]>(['zodiac', 'beast', 'fowl', 'vermin', 'divine'])
|
||||
|
||||
function categoryLabel(cat: string): string {
|
||||
return t('config.ziHuaDictionary.category_label.' + cat)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await createAxios({
|
||||
url: '/admin/config.ZiHuaDictionary/index',
|
||||
method: 'get',
|
||||
})
|
||||
if (res.code === 1 && res.data) {
|
||||
const list = res.data.items as Item[]
|
||||
items.value = Array.isArray(list) ? list : []
|
||||
if (Array.isArray(res.data.categories) && res.data.categories.length) {
|
||||
categories.value = res.data.categories as string[]
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
if (!auth('save')) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await createAxios({
|
||||
url: '/admin/config.ZiHuaDictionary/save',
|
||||
method: 'post',
|
||||
data: { items: items.value },
|
||||
showSuccessMessage: true,
|
||||
})
|
||||
await load()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.zi-hua-dict-page {
|
||||
.toolbar {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('game.betOrder.quick Search Fields') })"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('order.betOrder.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
@@ -20,7 +20,7 @@ import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'game/betOrder',
|
||||
name: 'order/betOrder',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -53,14 +53,14 @@ function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
}
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/game.BetOrder/'),
|
||||
new baTableApi('/admin/order.BetOrder/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ label: t('game.betOrder.id'), prop: 'id', align: 'center', width: 100, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('game.betOrder.period_id'), prop: 'period_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{ label: t('order.betOrder.id'), prop: 'id', align: 'center', width: 100, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('order.betOrder.period_id'), prop: 'period_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.betOrder.period_no'),
|
||||
label: t('order.betOrder.period_no'),
|
||||
prop: 'period_no',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
@@ -68,7 +68,7 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.gamePeriod.period_no'),
|
||||
label: t('order.betOrder.gamePeriod_period_no'),
|
||||
prop: 'gamePeriod.period_no',
|
||||
align: 'center',
|
||||
minWidth: 160,
|
||||
@@ -77,25 +77,25 @@ const baTable = new baTableClass(
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.gamePeriod.status'),
|
||||
label: t('order.betOrder.gamePeriod_status'),
|
||||
prop: 'gamePeriod.status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: {
|
||||
'0': t('game.period.status 0'),
|
||||
'1': t('game.period.status 1'),
|
||||
'2': t('game.period.status 2'),
|
||||
'3': t('game.period.status 3'),
|
||||
'4': t('game.period.status 4'),
|
||||
'5': t('game.period.status 5'),
|
||||
'0': '下注开放',
|
||||
'1': '已封盘',
|
||||
'2': '算票中',
|
||||
'3': '派彩中',
|
||||
'4': '已结束',
|
||||
'5': '已作废',
|
||||
},
|
||||
},
|
||||
{ label: t('game.betOrder.user_id'), prop: 'user_id', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{ label: t('order.betOrder.user_id'), prop: 'user_id', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.betOrder.gameUser.username'),
|
||||
prop: 'gameUser.username',
|
||||
label: t('order.betOrder.user_username'),
|
||||
prop: 'user.username',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
@@ -103,7 +103,7 @@ const baTable = new baTableClass(
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.channel.name'),
|
||||
label: t('order.betOrder.channel_name'),
|
||||
prop: 'channel.name',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
@@ -112,7 +112,7 @@ const baTable = new baTableClass(
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.pick_numbers'),
|
||||
label: t('order.betOrder.pick_numbers'),
|
||||
prop: 'pick_numbers',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -120,37 +120,37 @@ const baTable = new baTableClass(
|
||||
formatter: formatPickNumbers,
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.unit_amount'),
|
||||
label: t('order.betOrder.unit_amount'),
|
||||
prop: 'unit_amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{ label: t('game.betOrder.pick_count'), prop: 'pick_count', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{ label: t('order.betOrder.pick_count'), prop: 'pick_count', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.betOrder.total_amount'),
|
||||
label: t('order.betOrder.total_amount'),
|
||||
prop: 'total_amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{ label: t('game.betOrder.streak_at_bet'), prop: 'streak_at_bet', align: 'center', width: 110, operator: 'RANGE' },
|
||||
{ label: t('order.betOrder.streak_at_bet'), prop: 'streak_at_bet', align: 'center', width: 110, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.betOrder.is_auto'),
|
||||
label: t('order.betOrder.is_auto'),
|
||||
prop: 'is_auto',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: {
|
||||
'0': t('game.betOrder.is_auto 0'),
|
||||
'1': t('game.betOrder.is_auto 1'),
|
||||
'0': t('order.betOrder.is_auto 0'),
|
||||
'1': t('order.betOrder.is_auto 1'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.win_amount'),
|
||||
label: t('order.betOrder.win_amount'),
|
||||
prop: 'win_amount',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
@@ -158,7 +158,7 @@ const baTable = new baTableClass(
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.jackpot_extra_amount'),
|
||||
label: t('order.betOrder.jackpot_extra_amount'),
|
||||
prop: 'jackpot_extra_amount',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -166,20 +166,20 @@ const baTable = new baTableClass(
|
||||
formatter: formatAmount,
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.status'),
|
||||
label: t('order.betOrder.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: {
|
||||
'1': t('game.betOrder.status 1'),
|
||||
'2': t('game.betOrder.status 2'),
|
||||
'3': t('game.betOrder.status 3'),
|
||||
'1': t('order.betOrder.status 1'),
|
||||
'2': t('order.betOrder.status 2'),
|
||||
'3': t('order.betOrder.status 3'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.idempotency_key'),
|
||||
label: t('order.betOrder.idempotency_key'),
|
||||
prop: 'idempotency_key',
|
||||
align: 'center',
|
||||
minWidth: 140,
|
||||
@@ -188,7 +188,7 @@ const baTable = new baTableClass(
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.create_time'),
|
||||
label: t('order.betOrder.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
@@ -199,7 +199,7 @@ const baTable = new baTableClass(
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('game.betOrder.update_time'),
|
||||
label: t('order.betOrder.update_time'),
|
||||
prop: 'update_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
@@ -228,3 +228,6 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
|
||||
153
web/src/views/backend/order/depositOrder/index.vue
Normal file
153
web/src/views/backend/order/depositOrder/index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('order.depositOrder.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'order/depositOrder',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/order.DepositOrder/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('order.depositOrder.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('order.depositOrder.order_no'),
|
||||
prop: 'order_no',
|
||||
align: 'center',
|
||||
minWidth: 170,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{ label: t('order.depositOrder.user_id'), prop: 'user_id', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{
|
||||
label: t('order.depositOrder.user_username'),
|
||||
prop: 'user.username',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
render: 'tags',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.channel_name'),
|
||||
prop: 'channel.name',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
render: 'tags',
|
||||
},
|
||||
{ label: t('order.depositOrder.amount'), prop: 'amount', align: 'center', minWidth: 110, operator: 'RANGE' },
|
||||
{
|
||||
label: t('order.depositOrder.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: {
|
||||
'0': t('order.depositOrder.status 0'),
|
||||
'1': t('order.depositOrder.status 1'),
|
||||
'2': t('order.depositOrder.status 2'),
|
||||
'3': t('order.depositOrder.status 3'),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.pay_channel'),
|
||||
prop: 'pay_channel',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.pay_time'),
|
||||
prop: 'pay_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.remark'),
|
||||
prop: 'remark',
|
||||
align: 'center',
|
||||
minWidth: 150,
|
||||
operator: 'LIKE',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('order.depositOrder.update_time'),
|
||||
prop: 'update_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
operator: 'RANGE',
|
||||
comSearchRender: 'datetime',
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{ label: t('Operate'), align: 'center', width: 90, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: 0, amount: '0.0000' },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
48
web/src/views/backend/order/depositOrder/popupForm.vue
Normal file
48
web/src/views/backend/order/depositOrder/popupForm.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="['Add', 'Edit'].includes(baTable.form.operate!)" @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 v-loading="baTable.form.loading" 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" @submit.prevent="" @keyup.enter="baTable.onSubmit(formRef)" :model="baTable.form.items" :label-position="config.layout.shrink ? 'top' : 'right'" :label-width="baTable.form.labelWidth + 'px'" :rules="rules">
|
||||
<FormItem :label="t('order.depositOrder.order_no')" type="string" v-model="baTable.form.items!.order_no" prop="order_no" />
|
||||
<FormItem :label="t('order.depositOrder.user_id')" type="number" v-model="baTable.form.items!.user_id" prop="user_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('order.depositOrder.channel_id')" type="number" v-model="baTable.form.items!.channel_id" prop="channel_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('order.depositOrder.amount')" type="number" v-model="baTable.form.items!.amount" prop="amount" :input-attr="{ step: 0.0001, precision: 4, min: 0 }" />
|
||||
<FormItem :label="t('order.depositOrder.status')" type="radio" v-model="baTable.form.items!.status" prop="status" :input-attr="{ content: { '0': t('order.depositOrder.status 0'), '1': t('order.depositOrder.status 1'), '2': t('order.depositOrder.status 2'), '3': t('order.depositOrder.status 3') } }" />
|
||||
<FormItem :label="t('order.depositOrder.pay_channel')" type="string" v-model="baTable.form.items!.pay_channel" prop="pay_channel" />
|
||||
<FormItem :label="t('order.depositOrder.pay_time')" type="datetime" v-model="baTable.form.items!.pay_time" prop="pay_time" />
|
||||
<FormItem :label="t('order.depositOrder.remark')" type="textarea" v-model="baTable.form.items!.remark" prop="remark" :input-attr="{ rows: 2 }" />
|
||||
</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" @click="baTable.onSubmit(formRef)" type="primary">{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
order_no: [{ required: true, message: t('Please input field', { field: t('order.depositOrder.order_no') }) }],
|
||||
user_id: [{ required: true, message: t('Please input field', { field: t('order.depositOrder.user_id') }) }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
82
web/src/views/backend/order/withdrawOrder/index.vue
Normal file
82
web/src/views/backend/order/withdrawOrder/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('order.withdrawOrder.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
|
||||
<PopupForm />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'order/withdrawOrder',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete'])
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/order.WithdrawOrder/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('order.withdrawOrder.id'), prop: 'id', align: 'center', width: 80, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('order.withdrawOrder.order_no'), prop: 'order_no', align: 'center', minWidth: 170, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||
{ label: t('order.withdrawOrder.user_id'), prop: 'user_id', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{ label: t('order.withdrawOrder.user_username'), prop: 'user.username', align: 'center', minWidth: 110, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), render: 'tags' },
|
||||
{ label: t('order.withdrawOrder.channel_name'), prop: 'channel.name', align: 'center', minWidth: 110, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), render: 'tags' },
|
||||
{ label: t('order.withdrawOrder.amount'), prop: 'amount', align: 'center', minWidth: 110, operator: 'RANGE' },
|
||||
{ label: t('order.withdrawOrder.fee'), prop: 'fee', align: 'center', minWidth: 110, operator: 'RANGE' },
|
||||
{ label: t('order.withdrawOrder.actual_amount'), prop: 'actual_amount', align: 'center', minWidth: 110, operator: 'RANGE' },
|
||||
{
|
||||
label: t('order.withdrawOrder.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
operator: 'eq',
|
||||
render: 'tag',
|
||||
replaceValue: { '0': t('order.withdrawOrder.status 0'), '1': t('order.withdrawOrder.status 1'), '2': t('order.withdrawOrder.status 2'), '3': t('order.withdrawOrder.status 3') },
|
||||
},
|
||||
{ label: t('order.withdrawOrder.review_admin_username'), prop: 'reviewAdmin.username', align: 'center', minWidth: 100, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), render: 'tags' },
|
||||
{ label: t('order.withdrawOrder.review_time'), prop: 'review_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 170, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('order.withdrawOrder.remark'), prop: 'remark', align: 'center', minWidth: 150, operator: 'LIKE', operatorPlaceholder: t('Fuzzy query'), showOverflowTooltip: true },
|
||||
{ label: t('order.withdrawOrder.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 170, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('order.withdrawOrder.update_time'), prop: 'update_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 170, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
|
||||
{ label: t('Operate'), align: 'center', width: 90, render: 'buttons', buttons: optButtons, operator: false, fixed: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
defaultItems: { status: 0, amount: '0.0000', fee: '0.0000', actual_amount: '0.0000' },
|
||||
}
|
||||
)
|
||||
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
baTable.initSort()
|
||||
baTable.dragSort()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
50
web/src/views/backend/order/withdrawOrder/popupForm.vue
Normal file
50
web/src/views/backend/order/withdrawOrder/popupForm.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="['Add', 'Edit'].includes(baTable.form.operate!)" @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 v-loading="baTable.form.loading" 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" @submit.prevent="" @keyup.enter="baTable.onSubmit(formRef)" :model="baTable.form.items" :label-position="config.layout.shrink ? 'top' : 'right'" :label-width="baTable.form.labelWidth + 'px'" :rules="rules">
|
||||
<FormItem :label="t('order.withdrawOrder.order_no')" type="string" v-model="baTable.form.items!.order_no" prop="order_no" />
|
||||
<FormItem :label="t('order.withdrawOrder.user_id')" type="number" v-model="baTable.form.items!.user_id" prop="user_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.channel_id')" type="number" v-model="baTable.form.items!.channel_id" prop="channel_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.amount')" type="number" v-model="baTable.form.items!.amount" prop="amount" :input-attr="{ step: 0.0001, precision: 4, min: 0 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.fee')" type="number" v-model="baTable.form.items!.fee" prop="fee" :input-attr="{ step: 0.0001, precision: 4, min: 0 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.actual_amount')" type="number" v-model="baTable.form.items!.actual_amount" prop="actual_amount" :input-attr="{ step: 0.0001, precision: 4, min: 0 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.status')" type="radio" v-model="baTable.form.items!.status" prop="status" :input-attr="{ content: { '0': t('order.withdrawOrder.status 0'), '1': t('order.withdrawOrder.status 1'), '2': t('order.withdrawOrder.status 2'), '3': t('order.withdrawOrder.status 3') } }" />
|
||||
<FormItem :label="t('order.withdrawOrder.review_admin_id')" type="number" v-model="baTable.form.items!.review_admin_id" prop="review_admin_id" :input-attr="{ min: 1, step: 1 }" />
|
||||
<FormItem :label="t('order.withdrawOrder.review_time')" type="datetime" v-model="baTable.form.items!.review_time" prop="review_time" />
|
||||
<FormItem :label="t('order.withdrawOrder.remark')" type="textarea" v-model="baTable.form.items!.remark" prop="remark" :input-attr="{ rows: 2 }" />
|
||||
</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" @click="baTable.onSubmit(formRef)" type="primary">{{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormItemRule } from 'element-plus'
|
||||
import { inject, reactive, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FormItem from '/@/components/formItem/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const config = useConfig()
|
||||
const formRef = useTemplateRef('formRef')
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
order_no: [{ required: true, message: t('Please input field', { field: t('order.withdrawOrder.order_no') }) }],
|
||||
user_id: [{ required: true, message: t('Please input field', { field: t('order.withdrawOrder.user_id') }) }],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('game.walletRecord.quick Search Fields') })"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('record.userWalletRecord.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
@@ -20,7 +20,7 @@ import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'game/walletRecord',
|
||||
name: 'record/userWalletRecord',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -39,34 +39,34 @@ function formatAmount(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
}
|
||||
|
||||
const bizReplace = {
|
||||
deposit: t('game.walletRecord.biz deposit'),
|
||||
withdraw: t('game.walletRecord.biz withdraw'),
|
||||
withdraw_freeze: t('game.walletRecord.biz withdraw_freeze'),
|
||||
withdraw_unfreeze: t('game.walletRecord.biz withdraw_unfreeze'),
|
||||
platform_in: t('game.walletRecord.biz platform_in'),
|
||||
platform_out: t('game.walletRecord.biz platform_out'),
|
||||
admin_credit: t('game.walletRecord.biz admin_credit'),
|
||||
admin_deduct: t('game.walletRecord.biz admin_deduct'),
|
||||
bet: t('game.walletRecord.biz bet'),
|
||||
payout: t('game.walletRecord.biz payout'),
|
||||
fee: t('game.walletRecord.biz fee'),
|
||||
void_refund: t('game.walletRecord.biz void_refund'),
|
||||
adjust: t('game.walletRecord.biz adjust'),
|
||||
deposit: t('record.userWalletRecord.biz deposit'),
|
||||
withdraw: t('record.userWalletRecord.biz withdraw'),
|
||||
withdraw_freeze: t('record.userWalletRecord.biz withdraw_freeze'),
|
||||
withdraw_unfreeze: t('record.userWalletRecord.biz withdraw_unfreeze'),
|
||||
platform_in: t('record.userWalletRecord.biz platform_in'),
|
||||
platform_out: t('record.userWalletRecord.biz platform_out'),
|
||||
admin_credit: t('record.userWalletRecord.biz admin_credit'),
|
||||
admin_deduct: t('record.userWalletRecord.biz admin_deduct'),
|
||||
bet: t('record.userWalletRecord.biz bet'),
|
||||
payout: t('record.userWalletRecord.biz payout'),
|
||||
fee: t('record.userWalletRecord.biz fee'),
|
||||
void_refund: t('record.userWalletRecord.biz void_refund'),
|
||||
adjust: t('record.userWalletRecord.biz adjust'),
|
||||
}
|
||||
|
||||
const dirReplace = {
|
||||
'1': t('game.walletRecord.direction in'),
|
||||
'2': t('game.walletRecord.direction out'),
|
||||
'1': t('record.userWalletRecord.direction in'),
|
||||
'2': t('record.userWalletRecord.direction out'),
|
||||
}
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/game.UserWalletRecord/'),
|
||||
new baTableApi('/admin/record.UserWalletRecord/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ label: t('game.walletRecord.id'), prop: 'id', align: 'center', width: 100, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('record.userWalletRecord.id'), prop: 'id', align: 'center', width: 100, operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('game.walletRecord.user_id'),
|
||||
label: t('record.userWalletRecord.user_id'),
|
||||
prop: 'user_id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
@@ -74,8 +74,8 @@ const baTable = new baTableClass(
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.game_user__username'),
|
||||
prop: 'gameUser.username',
|
||||
label: t('record.userWalletRecord.user__username'),
|
||||
prop: 'user.username',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
@@ -84,7 +84,7 @@ const baTable = new baTableClass(
|
||||
comSearchRender: 'string',
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.channel__name'),
|
||||
label: t('record.userWalletRecord.channel__name'),
|
||||
prop: 'channel.name',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
@@ -94,7 +94,7 @@ const baTable = new baTableClass(
|
||||
comSearchRender: 'string',
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.biz_type'),
|
||||
label: t('record.userWalletRecord.biz_type'),
|
||||
prop: 'biz_type',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -103,7 +103,7 @@ const baTable = new baTableClass(
|
||||
replaceValue: bizReplace,
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.direction'),
|
||||
label: t('record.userWalletRecord.direction'),
|
||||
prop: 'direction',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
@@ -111,20 +111,20 @@ const baTable = new baTableClass(
|
||||
render: 'tag',
|
||||
replaceValue: dirReplace,
|
||||
},
|
||||
{ label: t('game.walletRecord.amount'), prop: 'amount', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{ label: t('game.walletRecord.balance_before'), prop: 'balance_before', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{ label: t('game.walletRecord.balance_after'), prop: 'balance_after', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{ label: t('record.userWalletRecord.amount'), prop: 'amount', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{ label: t('record.userWalletRecord.balance_before'), prop: 'balance_before', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{ label: t('record.userWalletRecord.balance_after'), prop: 'balance_after', align: 'center', minWidth: 110, operator: 'RANGE', formatter: formatAmount },
|
||||
{
|
||||
label: t('game.walletRecord.ref_type'),
|
||||
label: t('record.userWalletRecord.ref_type'),
|
||||
prop: 'ref_type',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
showOverflowTooltip: true,
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{ label: t('game.walletRecord.ref_id'), prop: 'ref_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{ label: t('record.userWalletRecord.ref_id'), prop: 'ref_id', align: 'center', width: 100, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.walletRecord.idempotency_key'),
|
||||
label: t('record.userWalletRecord.idempotency_key'),
|
||||
prop: 'idempotency_key',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -132,7 +132,7 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.operator_admin__username'),
|
||||
label: t('record.userWalletRecord.operator_admin__username'),
|
||||
prop: 'operatorAdmin.username',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
@@ -142,7 +142,7 @@ const baTable = new baTableClass(
|
||||
comSearchRender: 'string',
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.remark'),
|
||||
label: t('record.userWalletRecord.remark'),
|
||||
prop: 'remark',
|
||||
align: 'center',
|
||||
minWidth: 140,
|
||||
@@ -150,7 +150,7 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.walletRecord.create_time'),
|
||||
label: t('record.userWalletRecord.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
@@ -178,3 +178,5 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="default-main ba-table-box">
|
||||
<el-alert class="ba-table-alert" v-if="baTable.table.remark" :title="baTable.table.remark" type="info" show-icon />
|
||||
|
||||
<TableHeader
|
||||
:buttons="['refresh', 'add', 'edit', 'delete', 'comSearch', 'quickSearch', 'columnDisplay']"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('game.user.quick Search Fields') })"
|
||||
:quick-search-placeholder="t('Quick search placeholder', { fields: t('user.user.quick Search Fields') })"
|
||||
></TableHeader>
|
||||
|
||||
<Table ref="tableRef"></Table>
|
||||
@@ -24,7 +24,7 @@ import Table from '/@/components/table/index.vue'
|
||||
import baTableClass from '/@/utils/baTable'
|
||||
|
||||
defineOptions({
|
||||
name: 'game/user',
|
||||
name: 'user/user',
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -43,36 +43,36 @@ function formatCoin(_row: anyObj, _column: any, cellValue: unknown) {
|
||||
return n.toFixed(4)
|
||||
}
|
||||
|
||||
/** 返回多标签文案数组,供 render: tags 使用 */
|
||||
/** 杩斿洖澶氭爣绛炬枃妗堟暟缁勶紝渚?render: tags 浣跨敤 */
|
||||
function formatRiskFlags(row: anyObj, _column: any, cellValue: unknown) {
|
||||
const raw = cellValue !== undefined && cellValue !== null && cellValue !== '' ? cellValue : row.risk_flags
|
||||
const f = typeof raw === 'number' ? raw : parseInt(String(raw ?? '0'), 10) || 0
|
||||
const parts: string[] = []
|
||||
if (f & 1) parts.push(t('game.user.risk_no_login'))
|
||||
if (f & 2) parts.push(t('game.user.risk_no_bet'))
|
||||
if (f & 4) parts.push(t('game.user.risk_no_withdraw'))
|
||||
if (!parts.length) parts.push(t('game.user.risk_none'))
|
||||
if (f & 1) parts.push(t('user.user.risk_no_login'))
|
||||
if (f & 2) parts.push(t('user.user.risk_no_bet'))
|
||||
if (f & 4) parts.push(t('user.user.risk_no_withdraw'))
|
||||
if (!parts.length) parts.push(t('user.user.risk_none'))
|
||||
return parts
|
||||
}
|
||||
|
||||
const baTable = new baTableClass(
|
||||
new baTableApi('/admin/game.User/'),
|
||||
new baTableApi('/admin/user.User/'),
|
||||
{
|
||||
pk: 'id',
|
||||
column: [
|
||||
{ type: 'selection', align: 'center', operator: false },
|
||||
{ label: t('game.user.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
|
||||
{ label: t('user.user.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
|
||||
{
|
||||
label: t('game.user.username'),
|
||||
label: t('user.user.username'),
|
||||
prop: 'username',
|
||||
align: 'center',
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
sortable: false,
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{ label: t('game.user.phone'), prop: 'phone', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
|
||||
{ label: t('user.user.phone'), prop: 'phone', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
|
||||
{
|
||||
label: t('game.user.email'),
|
||||
label: t('user.user.email'),
|
||||
prop: 'email',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -81,7 +81,7 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.user.head_image'),
|
||||
label: t('user.user.head_image'),
|
||||
prop: 'head_image',
|
||||
align: 'center',
|
||||
width: 72,
|
||||
@@ -89,7 +89,7 @@ const baTable = new baTableClass(
|
||||
render: 'image',
|
||||
},
|
||||
{
|
||||
label: t('game.user.uuid'),
|
||||
label: t('user.user.uuid'),
|
||||
prop: 'uuid',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
@@ -98,16 +98,16 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.user.register_invite_code'),
|
||||
label: t('user.user.register_invite_code'),
|
||||
prop: 'register_invite_code',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
showOverflowTooltip: true,
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{ label: t('game.user.coin'), prop: 'coin', align: 'center', sortable: false, operator: 'RANGE', formatter: formatCoin },
|
||||
{ label: t('user.user.coin'), prop: 'coin', align: 'center', sortable: false, operator: 'RANGE', formatter: formatCoin },
|
||||
{
|
||||
label: t('game.user.total_deposit_coin'),
|
||||
label: t('user.user.total_deposit_coin'),
|
||||
prop: 'total_deposit_coin',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
@@ -116,7 +116,7 @@ const baTable = new baTableClass(
|
||||
formatter: formatCoin,
|
||||
},
|
||||
{
|
||||
label: t('game.user.total_valid_bet_coin'),
|
||||
label: t('user.user.total_valid_bet_coin'),
|
||||
prop: 'total_valid_bet_coin',
|
||||
align: 'center',
|
||||
minWidth: 110,
|
||||
@@ -125,7 +125,7 @@ const baTable = new baTableClass(
|
||||
formatter: formatCoin,
|
||||
},
|
||||
{
|
||||
label: t('game.user.risk_flags'),
|
||||
label: t('user.user.risk_flags'),
|
||||
prop: 'risk_flags',
|
||||
align: 'center',
|
||||
render: 'tags',
|
||||
@@ -133,15 +133,15 @@ const baTable = new baTableClass(
|
||||
operator: false,
|
||||
formatter: formatRiskFlags,
|
||||
custom: {
|
||||
[t('game.user.risk_none')]: 'primary',
|
||||
[t('game.user.risk_no_login')]: 'danger',
|
||||
[t('game.user.risk_no_bet')]: 'danger',
|
||||
[t('game.user.risk_no_withdraw')]: 'danger',
|
||||
[t('user.user.risk_none')]: 'primary',
|
||||
[t('user.user.risk_no_login')]: 'danger',
|
||||
[t('user.user.risk_no_bet')]: 'danger',
|
||||
[t('user.user.risk_no_withdraw')]: 'danger',
|
||||
},
|
||||
},
|
||||
{ label: t('game.user.current_streak'), prop: 'current_streak', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{ label: t('user.user.current_streak'), prop: 'current_streak', align: 'center', width: 90, operator: 'RANGE' },
|
||||
{
|
||||
label: t('game.user.last_bet_period_no'),
|
||||
label: t('user.user.last_bet_period_no'),
|
||||
prop: 'last_bet_period_no',
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
@@ -149,16 +149,16 @@ const baTable = new baTableClass(
|
||||
operator: 'LIKE',
|
||||
},
|
||||
{
|
||||
label: t('game.user.status'),
|
||||
label: t('user.user.status'),
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
operator: 'eq',
|
||||
sortable: false,
|
||||
render: 'switch',
|
||||
replaceValue: { '0': t('game.user.status 0'), '1': t('game.user.status 1') },
|
||||
replaceValue: { '0': t('user.user.status 0'), '1': t('user.user.status 1') },
|
||||
},
|
||||
{
|
||||
label: t('game.user.channel__name'),
|
||||
label: t('user.user.channel__name'),
|
||||
prop: 'channel.name',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
@@ -168,7 +168,7 @@ const baTable = new baTableClass(
|
||||
comSearchRender: 'string',
|
||||
},
|
||||
{
|
||||
label: t('game.user.admin__username'),
|
||||
label: t('user.user.admin__username'),
|
||||
prop: 'admin.username',
|
||||
align: 'center',
|
||||
minWidth: 90,
|
||||
@@ -185,7 +185,7 @@ const baTable = new baTableClass(
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('game.user.remark'),
|
||||
label: t('user.user.remark'),
|
||||
prop: 'remark',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
@@ -193,7 +193,7 @@ const baTable = new baTableClass(
|
||||
operatorPlaceholder: t('Fuzzy query'),
|
||||
},
|
||||
{
|
||||
label: t('game.user.create_time'),
|
||||
label: t('user.user.create_time'),
|
||||
prop: 'create_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
@@ -204,7 +204,7 @@ const baTable = new baTableClass(
|
||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
||||
},
|
||||
{
|
||||
label: t('game.user.update_time'),
|
||||
label: t('user.user.update_time'),
|
||||
prop: 'update_time',
|
||||
align: 'center',
|
||||
render: 'datetime',
|
||||
@@ -247,3 +247,5 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
|
||||
@@ -27,47 +27,47 @@
|
||||
:rules="rules"
|
||||
>
|
||||
<!-- <el-alert class="game-user-form-tip" type="info" :closable="false" show-icon>-->
|
||||
<!-- {{ t('game.user.form_tip') }}-->
|
||||
<!-- {{ t('user.user.form_tip') }}-->
|
||||
<!-- </el-alert>-->
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_basic') }}</el-divider>
|
||||
<el-divider content-position="left">{{ t('user.user.section_basic') }}</el-divider>
|
||||
<FormItem
|
||||
:label="t('game.user.username')"
|
||||
:label="t('user.user.username')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.username"
|
||||
prop="username"
|
||||
:placeholder="t('Please input field', { field: t('game.user.username') })"
|
||||
:placeholder="t('Please input field', { field: t('user.user.username') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.password')"
|
||||
:label="t('user.user.password')"
|
||||
type="password"
|
||||
v-model="baTable.form.items!.password"
|
||||
prop="password"
|
||||
:placeholder="t('Please input field', { field: t('game.user.password') })"
|
||||
:placeholder="t('Please input field', { field: t('user.user.password') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.phone')"
|
||||
:label="t('user.user.phone')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.phone"
|
||||
prop="phone"
|
||||
:placeholder="t('Please input field', { field: t('game.user.phone') })"
|
||||
:placeholder="t('Please input field', { field: t('user.user.phone') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.email')"
|
||||
:label="t('user.user.email')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.email"
|
||||
prop="email"
|
||||
:placeholder="t('game.user.email_placeholder')"
|
||||
:placeholder="t('user.user.email_placeholder')"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.head_image')"
|
||||
:label="t('user.user.head_image')"
|
||||
type="image"
|
||||
v-model="baTable.form.items!.head_image"
|
||||
prop="head_image"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_admin_attribution') }}</el-divider>
|
||||
<el-form-item :label="t('game.user.admin_affiliation')" prop="admin_id">
|
||||
<el-divider content-position="left">{{ t('user.user.section_admin_attribution') }}</el-divider>
|
||||
<el-form-item :label="t('user.user.admin_affiliation')" prop="admin_id">
|
||||
<el-tree-select
|
||||
v-model="baTable.form.items!.admin_id"
|
||||
class="w100"
|
||||
@@ -76,55 +76,55 @@
|
||||
:data="adminScopeTree"
|
||||
:props="treeProps"
|
||||
:render-after-expand="false"
|
||||
:placeholder="t('game.user.admin_affiliation_placeholder')"
|
||||
:placeholder="t('user.user.admin_affiliation_placeholder')"
|
||||
@update:model-value="onAdminTreeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<FormItem
|
||||
v-if="baTable.form.operate === 'Edit'"
|
||||
:label="t('game.user.uuid')"
|
||||
:label="t('user.user.uuid')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.uuid"
|
||||
prop="uuid"
|
||||
:input-attr="{ readonly: true, disabled: true }"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_register') }}</el-divider>
|
||||
<el-divider content-position="left">{{ t('user.user.section_register') }}</el-divider>
|
||||
<FormItem
|
||||
:label="t('game.user.register_invite_code')"
|
||||
:label="t('user.user.register_invite_code')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.register_invite_code"
|
||||
prop="register_invite_code"
|
||||
:input-attr="{ readonly: true }"
|
||||
:placeholder="t('game.user.register_invite_code_auto_placeholder')"
|
||||
:placeholder="t('user.user.register_invite_code_auto_placeholder')"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_finance') }}</el-divider>
|
||||
<el-divider content-position="left">{{ t('user.user.section_finance') }}</el-divider>
|
||||
<FormItem
|
||||
:label="t('game.user.coin')"
|
||||
:label="t('user.user.coin')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.coin"
|
||||
prop="coin"
|
||||
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
|
||||
:placeholder="t('game.user.coin_placeholder')"
|
||||
:placeholder="t('user.user.coin_placeholder')"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.total_deposit_coin')"
|
||||
:label="t('user.user.total_deposit_coin')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.total_deposit_coin"
|
||||
prop="total_deposit_coin"
|
||||
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.total_valid_bet_coin')"
|
||||
:label="t('user.user.total_valid_bet_coin')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.total_valid_bet_coin"
|
||||
prop="total_valid_bet_coin"
|
||||
:input-attr="{ step: 0.0001, min: 0, precision: 4 }"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_risk') }}</el-divider>
|
||||
<el-form-item :label="t('game.user.risk_flags')">
|
||||
<el-divider content-position="left">{{ t('user.user.section_risk') }}</el-divider>
|
||||
<el-form-item :label="t('user.user.risk_flags')">
|
||||
<div class="risk-flag-row">
|
||||
<el-tag
|
||||
:type="riskBits.noLogin ? 'danger' : 'info'"
|
||||
@@ -132,7 +132,7 @@
|
||||
class="risk-flag-tag"
|
||||
@click="riskBits.noLogin = !riskBits.noLogin"
|
||||
>
|
||||
{{ t('game.user.risk_no_login') }}
|
||||
{{ t('user.user.risk_no_login') }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="riskBits.noBet ? 'danger' : 'info'"
|
||||
@@ -140,7 +140,7 @@
|
||||
class="risk-flag-tag"
|
||||
@click="riskBits.noBet = !riskBits.noBet"
|
||||
>
|
||||
{{ t('game.user.risk_no_bet') }}
|
||||
{{ t('user.user.risk_no_bet') }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="riskBits.noWithdraw ? 'danger' : 'info'"
|
||||
@@ -148,44 +148,44 @@
|
||||
class="risk-flag-tag"
|
||||
@click="riskBits.noWithdraw = !riskBits.noWithdraw"
|
||||
>
|
||||
{{ t('game.user.risk_no_withdraw') }}
|
||||
{{ t('user.user.risk_no_withdraw') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_streak') }}</el-divider>
|
||||
<el-divider content-position="left">{{ t('user.user.section_streak') }}</el-divider>
|
||||
<FormItem
|
||||
:label="t('game.user.current_streak')"
|
||||
:label="t('user.user.current_streak')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.current_streak"
|
||||
prop="current_streak"
|
||||
:input-attr="{ step: 1, min: 0, precision: 0 }"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.last_bet_period_no')"
|
||||
:label="t('user.user.last_bet_period_no')"
|
||||
type="string"
|
||||
v-model="baTable.form.items!.last_bet_period_no"
|
||||
prop="last_bet_period_no"
|
||||
:placeholder="t('game.user.last_bet_period_no_placeholder')"
|
||||
:placeholder="t('user.user.last_bet_period_no_placeholder')"
|
||||
/>
|
||||
|
||||
<el-divider content-position="left">{{ t('game.user.section_other') }}</el-divider>
|
||||
<el-divider content-position="left">{{ t('user.user.section_other') }}</el-divider>
|
||||
<FormItem
|
||||
:label="t('game.user.remark')"
|
||||
:label="t('user.user.remark')"
|
||||
type="textarea"
|
||||
v-model="baTable.form.items!.remark"
|
||||
prop="remark"
|
||||
:input-attr="{ rows: 3 }"
|
||||
@keyup.enter.stop=""
|
||||
@keyup.ctrl.enter="baTable.onSubmit(formRef)"
|
||||
:placeholder="t('Please input field', { field: t('game.user.remark') })"
|
||||
:placeholder="t('Please input field', { field: t('user.user.remark') })"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('game.user.status')"
|
||||
:label="t('user.user.status')"
|
||||
type="switch"
|
||||
v-model="baTable.form.items!.status"
|
||||
prop="status"
|
||||
:input-attr="{ content: { '0': t('game.user.status 0'), '1': t('game.user.status 1') } }"
|
||||
:input-attr="{ content: { '0': t('user.user.status 0'), '1': t('user.user.status 1') } }"
|
||||
/>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -263,7 +263,7 @@ function packRiskFlags(): number {
|
||||
return r
|
||||
}
|
||||
|
||||
/** 识别管理员叶子:value 非 group_* 且无子节点(不依赖 is_leaf,避免 el-tree 丢弃自定义字段) */
|
||||
/** 璇嗗埆绠$悊鍛樺彾瀛愶細value 闈?group_* 涓旀棤瀛愯妭鐐癸紙涓嶄緷璧?is_leaf锛岄伩鍏?el-tree 涓㈠純鑷畾涔夊瓧娈碉級 */
|
||||
function isAdminTreeLeaf(n: TreeNode): boolean {
|
||||
const v = n.value
|
||||
if (v === undefined || v === null) return false
|
||||
@@ -300,7 +300,7 @@ function buildAdminMapsFromTree(nodes: TreeNode[]) {
|
||||
return { mapCh, mapInv }
|
||||
}
|
||||
|
||||
/** 映射未命中时从原始树查找(防止 props 裁剪或异步时序问题) */
|
||||
/** 鏄犲皠鏈懡涓椂浠庡師濮嬫爲鏌ユ壘锛堥槻姝?props 瑁佸壀鎴栧紓姝ユ椂搴忛棶棰橈級 */
|
||||
function findAdminMetaInTree(nodes: TreeNode[], adminId: string): { channel_id?: number; invite_code: string } | null {
|
||||
const target = String(adminId).trim()
|
||||
for (const n of nodes) {
|
||||
@@ -329,7 +329,7 @@ function findAdminMetaInTree(nodes: TreeNode[], adminId: string): { channel_id?:
|
||||
|
||||
const loadAdminScopeTree = async () => {
|
||||
const res = await createAxios({
|
||||
url: '/admin/game.User/adminScopeTree',
|
||||
url: '/admin/user.User/adminScopeTree',
|
||||
method: 'get',
|
||||
})
|
||||
const list = (res.data?.list ?? []) as TreeNode[]
|
||||
@@ -389,7 +389,7 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 树数据异步加载完成后,根据已选管理员同步渠道与邀请码(编辑回显)
|
||||
// 鏍戞暟鎹紓姝ュ姞杞藉畬鎴愬悗锛屾牴鎹凡閫夌鐞嗗憳鍚屾娓犻亾涓庨個璇风爜锛堢紪杈戝洖鏄撅級
|
||||
watch(
|
||||
() => [baTable.form.items?.admin_id, adminScopeTree.value.length] as const,
|
||||
() => {
|
||||
@@ -426,7 +426,7 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 与 el-tree-select 的 value(字符串)一致,避免编辑回显选不中
|
||||
// 统一 el-tree-select 的 value 为字符串,避免编辑回显不选中
|
||||
watch(
|
||||
() => [baTable.form.operate, baTable.form.items?.id] as const,
|
||||
() => {
|
||||
@@ -445,7 +445,7 @@ const validatorGameUserPassword = (rule: any, val: string, callback: (error?: Er
|
||||
const v = typeof val === 'string' ? val.trim() : ''
|
||||
|
||||
if (operate === 'Add') {
|
||||
if (!v) return callback(new Error(t('Please input field', { field: t('game.user.password') })))
|
||||
if (!v) return callback(new Error(t('Please input field', { field: t('user.user.password') })))
|
||||
if (!regularPassword(v)) return callback(new Error(t('validate.Please enter the correct password')))
|
||||
return callback()
|
||||
}
|
||||
@@ -470,15 +470,15 @@ const decimalRule = (fieldTitle: string): FormItemRule => ({
|
||||
})
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
username: [buildValidatorData({ name: 'required', title: t('game.user.username') })],
|
||||
username: [buildValidatorData({ name: 'required', title: t('user.user.username') })],
|
||||
password: [{ validator: validatorGameUserPassword, trigger: 'blur' }],
|
||||
phone: [buildValidatorData({ name: 'required', title: t('game.user.phone') })],
|
||||
coin: [decimalRule(t('game.user.coin'))],
|
||||
total_deposit_coin: [decimalRule(t('game.user.total_deposit_coin'))],
|
||||
total_valid_bet_coin: [decimalRule(t('game.user.total_valid_bet_coin'))],
|
||||
admin_id: [buildValidatorData({ name: 'required', title: t('game.user.admin_affiliation') })],
|
||||
create_time: [buildValidatorData({ name: 'date', title: t('game.user.create_time') })],
|
||||
update_time: [buildValidatorData({ name: 'date', title: t('game.user.update_time') })],
|
||||
phone: [buildValidatorData({ name: 'required', title: t('user.user.phone') })],
|
||||
coin: [decimalRule(t('user.user.coin'))],
|
||||
total_deposit_coin: [decimalRule(t('user.user.total_deposit_coin'))],
|
||||
total_valid_bet_coin: [decimalRule(t('user.user.total_valid_bet_coin'))],
|
||||
admin_id: [buildValidatorData({ name: 'required', title: t('user.user.admin_affiliation') })],
|
||||
create_time: [buildValidatorData({ name: 'date', title: t('user.user.create_time') })],
|
||||
update_time: [buildValidatorData({ name: 'date', title: t('user.user.update_time') })],
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -497,3 +497,5 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user