[游戏管理]用户管理
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\admin\controller\auth;
|
namespace app\admin\controller\auth;
|
||||||
|
|
||||||
|
use ba\Random;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use support\think\Db;
|
use support\think\Db;
|
||||||
use support\validation\Validator;
|
use support\validation\Validator;
|
||||||
@@ -21,7 +22,7 @@ class Admin extends Backend
|
|||||||
|
|
||||||
protected array|string $quickSearchField = ['username', 'nickname'];
|
protected array|string $quickSearchField = ['username', 'nickname'];
|
||||||
|
|
||||||
protected string|int|bool $dataLimit = 'allAuthAndOthers';
|
protected string|int|bool $dataLimit = 'parent';
|
||||||
|
|
||||||
protected string $dataLimitField = 'id';
|
protected string $dataLimitField = 'id';
|
||||||
|
|
||||||
@@ -61,9 +62,18 @@ class Admin extends Backend
|
|||||||
$res = $query
|
$res = $query
|
||||||
->order($order)
|
->order($order)
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
|
$items = $res->items();
|
||||||
|
$topGroupUids = $this->getTopGroupUserMap(array_column($items, 'id'));
|
||||||
|
foreach ($items as &$item) {
|
||||||
|
$id = $item['id'] ?? null;
|
||||||
|
if ($id === 1 || isset($topGroupUids[$id])) {
|
||||||
|
$item['commission_rate'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($item);
|
||||||
|
|
||||||
return $this->success('', [
|
return $this->success('', [
|
||||||
'list' => $res->items(),
|
'list' => $items,
|
||||||
'total' => $res->total(),
|
'total' => $res->total(),
|
||||||
'remark' => get_route_remark(),
|
'remark' => get_route_remark(),
|
||||||
]);
|
]);
|
||||||
@@ -149,6 +159,10 @@ class Admin extends Backend
|
|||||||
if (!$data) {
|
if (!$data) {
|
||||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||||
}
|
}
|
||||||
|
$data = $this->normalizeSingleGroup($data);
|
||||||
|
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
|
||||||
|
return $this->error('请选择且仅选择一个角色组');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->modelValidate) {
|
if ($this->modelValidate) {
|
||||||
try {
|
try {
|
||||||
@@ -172,6 +186,25 @@ class Admin extends Backend
|
|||||||
|
|
||||||
$passwd = $data['password'] ?? '';
|
$passwd = $data['password'] ?? '';
|
||||||
$data = $this->excludeFields($data);
|
$data = $this->excludeFields($data);
|
||||||
|
$creatorChannelId = $this->getCreatorChannelId();
|
||||||
|
if (!$this->auth->isSuperAdmin()) {
|
||||||
|
if ($creatorChannelId === null || $creatorChannelId === '') {
|
||||||
|
return $this->error(__('You have no permission'));
|
||||||
|
}
|
||||||
|
$data['channel_id'] = $creatorChannelId;
|
||||||
|
$data['parent_admin_id'] = $this->auth->id;
|
||||||
|
}
|
||||||
|
$data['invite_code'] = $this->generateUniqueInviteCode();
|
||||||
|
$requireCommissionRate = $this->requireCommissionRate($data['group_arr'] ?? []);
|
||||||
|
if ($requireCommissionRate) {
|
||||||
|
if (!$this->isValidCommissionRate($data['commission_rate'] ?? null)) {
|
||||||
|
return $this->error(__('Please enter a valid commission rate for non-top role group'));
|
||||||
|
}
|
||||||
|
$commissionRes = $this->validateAdminCommissionByGroups($data['group_arr'] ?? [], floatval((string)$data['commission_rate']));
|
||||||
|
if ($commissionRes !== null) return $commissionRes;
|
||||||
|
} else {
|
||||||
|
$data['commission_rate'] = null;
|
||||||
|
}
|
||||||
$result = false;
|
$result = false;
|
||||||
if (!empty($data['group_arr'])) {
|
if (!empty($data['group_arr'])) {
|
||||||
$authRes = $this->checkGroupAuth($data['group_arr']);
|
$authRes = $this->checkGroupAuth($data['group_arr']);
|
||||||
@@ -230,6 +263,37 @@ class Admin extends Backend
|
|||||||
if (!$data) {
|
if (!$data) {
|
||||||
return $this->error(__('Parameter %s can not be empty', ['']));
|
return $this->error(__('Parameter %s can not be empty', ['']));
|
||||||
}
|
}
|
||||||
|
$data = $this->normalizeSingleGroup($data);
|
||||||
|
if (!$this->hasSingleGroup($data['group_arr'] ?? null)) {
|
||||||
|
return $this->error('请选择且仅选择一个角色组');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未提交分红比例时,若角色组未变更则沿用数据库原值(避免表单单项 number 校验把空串判错)
|
||||||
|
$postedGroups = array_map('intval', $data['group_arr'] ?? []);
|
||||||
|
$rowGroups = array_map('intval', $row->group_arr ?? []);
|
||||||
|
sort($postedGroups);
|
||||||
|
sort($rowGroups);
|
||||||
|
$sameGroups = $postedGroups === $rowGroups;
|
||||||
|
$postedCommission = $data['commission_rate'] ?? null;
|
||||||
|
if (($postedCommission === null || $postedCommission === '') && $sameGroups && $this->isValidCommissionRate($row['commission_rate'] ?? null)) {
|
||||||
|
$data['commission_rate'] = $row['commission_rate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前管理员编辑自身时,不允许修改角色组和分红比
|
||||||
|
if ((int)$this->auth->id === (int)$id) {
|
||||||
|
$postedGroups = $data['group_arr'] ?? [];
|
||||||
|
if (!is_array($postedGroups)) {
|
||||||
|
$postedGroups = [];
|
||||||
|
}
|
||||||
|
$originGroups = $row->group_arr ?? [];
|
||||||
|
sort($postedGroups);
|
||||||
|
sort($originGroups);
|
||||||
|
$postedRate = $data['commission_rate'] ?? null;
|
||||||
|
$originRate = $row['commission_rate'] ?? null;
|
||||||
|
if ($postedGroups !== $originGroups || (string)$postedRate !== (string)$originRate) {
|
||||||
|
return $this->error(__('You cannot modify your own management group!'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->modelValidate) {
|
if ($this->modelValidate) {
|
||||||
try {
|
try {
|
||||||
@@ -276,15 +340,29 @@ class Admin extends Backend
|
|||||||
if ($authRes !== null) return $authRes;
|
if ($authRes !== null) return $authRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Db::name('admin_group_access')
|
|
||||||
->where('uid', $id)
|
|
||||||
->delete();
|
|
||||||
|
|
||||||
$data = $this->excludeFields($data);
|
$data = $this->excludeFields($data);
|
||||||
|
unset($data['invite_code']);
|
||||||
|
$creatorChannelId = $this->getCreatorChannelId();
|
||||||
|
if (!$this->auth->isSuperAdmin() && $creatorChannelId !== null && $creatorChannelId !== '') {
|
||||||
|
$data['channel_id'] = $creatorChannelId;
|
||||||
|
}
|
||||||
|
$requireCommissionRate = $this->requireCommissionRate($data['group_arr'] ?? []);
|
||||||
|
if ($requireCommissionRate) {
|
||||||
|
if (!$this->isValidCommissionRate($data['commission_rate'] ?? null)) {
|
||||||
|
return $this->error(__('Please enter a valid commission rate for non-top role group'));
|
||||||
|
}
|
||||||
|
$commissionRes = $this->validateAdminCommissionByGroups($data['group_arr'] ?? [], floatval((string)$data['commission_rate']), intval((string)$id));
|
||||||
|
if ($commissionRes !== null) return $commissionRes;
|
||||||
|
} else {
|
||||||
|
$data['commission_rate'] = null;
|
||||||
|
}
|
||||||
$result = false;
|
$result = false;
|
||||||
$this->model->startTrans();
|
$this->model->startTrans();
|
||||||
try {
|
try {
|
||||||
$result = $row->save($data);
|
$result = $row->save($data);
|
||||||
|
Db::name('admin_group_access')
|
||||||
|
->where('uid', $id)
|
||||||
|
->delete();
|
||||||
if ($groupAccess) {
|
if ($groupAccess) {
|
||||||
Db::name('admin_group_access')->insertAll($groupAccess);
|
Db::name('admin_group_access')->insertAll($groupAccess);
|
||||||
}
|
}
|
||||||
@@ -357,7 +435,7 @@ class Admin extends Backend
|
|||||||
if ($this->auth->isSuperAdmin()) {
|
if ($this->auth->isSuperAdmin()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$authGroups = $this->auth->getAllAuthGroups('allAuthAndOthers');
|
$authGroups = $this->getManageableGroupIds();
|
||||||
foreach ($groups as $group) {
|
foreach ($groups as $group) {
|
||||||
if (!in_array($group, $authGroups)) {
|
if (!in_array($group, $authGroups)) {
|
||||||
return $this->error(__('You have no permission to add an administrator to this group!'));
|
return $this->error(__('You have no permission to add an administrator to this group!'));
|
||||||
@@ -365,4 +443,132 @@ class Admin extends Backend
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getManageableGroupIds(): array
|
||||||
|
{
|
||||||
|
if ($this->auth->isSuperAdmin()) {
|
||||||
|
return Db::name('admin_group')->where('status', 1)->column('id');
|
||||||
|
}
|
||||||
|
$own = Db::name('admin_group_access')->where('uid', $this->auth->id)->column('group_id');
|
||||||
|
$children = $this->auth->getAdminChildGroups();
|
||||||
|
return array_values(array_unique(array_merge($own, $children)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCreatorChannelId(): mixed
|
||||||
|
{
|
||||||
|
$currentAdmin = Db::name('admin')
|
||||||
|
->field(['id', 'channel_id'])
|
||||||
|
->where('id', $this->auth->id)
|
||||||
|
->find();
|
||||||
|
if ($currentAdmin && !empty($currentAdmin['channel_id'])) {
|
||||||
|
return $currentAdmin['channel_id'];
|
||||||
|
}
|
||||||
|
$channelId = Db::name('channel')
|
||||||
|
->where('top_admin_id', $this->auth->id)
|
||||||
|
->value('id');
|
||||||
|
return $channelId ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateUniqueInviteCode(): string
|
||||||
|
{
|
||||||
|
$tries = 0;
|
||||||
|
do {
|
||||||
|
$tries++;
|
||||||
|
$code = strtoupper(substr(Random::uuid(), 0, 8));
|
||||||
|
$exists = Db::name('admin')->where('invite_code', $code)->find();
|
||||||
|
} while ($exists && $tries < 20);
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function requireCommissionRate(array $groupIds): bool
|
||||||
|
{
|
||||||
|
if (!$groupIds) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$count = Db::name('admin_group')
|
||||||
|
->where('id', 'in', $groupIds)
|
||||||
|
->where('pid', '<>', 0)
|
||||||
|
->count();
|
||||||
|
return $count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isValidCommissionRate(mixed $value): bool
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$rate = trim((string)$value);
|
||||||
|
if (!preg_match('/^(100(\.00?)?|[0-9]{1,2}(\.[0-9]{1,2})?)$/', $rate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateAdminCommissionByGroups(array $groupIds, float $currentRate, ?int $excludeAdminId = null): ?Response
|
||||||
|
{
|
||||||
|
if (!$groupIds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$groups = Db::name('admin_group')
|
||||||
|
->where('id', 'in', $groupIds)
|
||||||
|
->where('pid', '<>', 0)
|
||||||
|
->column('name', 'id');
|
||||||
|
foreach ($groups as $groupId => $groupName) {
|
||||||
|
$query = Db::name('admin_group_access')->alias('aga')
|
||||||
|
->join('admin a', 'aga.uid = a.id')
|
||||||
|
->where('aga.group_id', intval((string)$groupId));
|
||||||
|
if ($excludeAdminId !== null) {
|
||||||
|
$query = $query->where('a.id', '<>', $excludeAdminId);
|
||||||
|
}
|
||||||
|
$sum = (float)$query->sum('a.commission_rate');
|
||||||
|
$remaining = 100 - $sum;
|
||||||
|
if ($currentRate > $remaining + 0.000001) {
|
||||||
|
$exceed = $currentRate - $remaining;
|
||||||
|
return $this->error(sprintf('角色组[%s]分红比例总和不能超过100%%,当前剩余 %.2f%%,本次超出 %.2f%%', $groupName, max(0, $remaining), $exceed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTopGroupUserMap(array $userIds): array
|
||||||
|
{
|
||||||
|
if (!$userIds) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$uids = Db::name('admin_group_access')->alias('aga')
|
||||||
|
->join('admin_group ag', 'aga.group_id = ag.id')
|
||||||
|
->where('aga.uid', 'in', $userIds)
|
||||||
|
->where('ag.pid', 0)
|
||||||
|
->column('aga.uid');
|
||||||
|
$map = [];
|
||||||
|
foreach ($uids as $uid) {
|
||||||
|
$map[$uid] = true;
|
||||||
|
}
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeSingleGroup(array $data): array
|
||||||
|
{
|
||||||
|
if (!array_key_exists('group_arr', $data)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
$group = $data['group_arr'];
|
||||||
|
if (is_array($group)) {
|
||||||
|
$data['group_arr'] = array_values(array_filter($group, static function ($item) {
|
||||||
|
return $item !== null && $item !== '';
|
||||||
|
}));
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
if ($group === null || $group === '') {
|
||||||
|
$data['group_arr'] = [];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
$data['group_arr'] = [$group];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasSingleGroup(mixed $groups): bool
|
||||||
|
{
|
||||||
|
return is_array($groups) && count($groups) === 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class User extends Backend
|
|||||||
|
|
||||||
protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time'];
|
protected array|string $preExcludeFields = ['id', 'uuid', 'create_time', 'update_time'];
|
||||||
|
|
||||||
protected array $withJoinTable = ['gameChannel', 'admin'];
|
protected array $withJoinTable = ['channel', 'admin'];
|
||||||
|
|
||||||
protected string|array $quickSearchField = ['id', 'username', 'phone'];
|
protected string|array $quickSearchField = ['id', 'username', 'phone'];
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class User extends Backend
|
|||||||
$data['password'] = hash_password($password);
|
$data['password'] = hash_password($password);
|
||||||
|
|
||||||
$username = $data['username'] ?? '';
|
$username = $data['username'] ?? '';
|
||||||
$channelId = $data['channel_id'] ?? ($data['game_channel_id'] ?? null);
|
$channelId = $data['channel_id'] ?? null;
|
||||||
if (!is_string($username) || trim($username) === '' || $channelId === null || $channelId === '') {
|
if (!is_string($username) || trim($username) === '' || $channelId === null || $channelId === '') {
|
||||||
return $this->error(__('Parameter %s can not be empty', ['username/channel_id']));
|
return $this->error(__('Parameter %s can not be empty', ['username/channel_id']));
|
||||||
}
|
}
|
||||||
@@ -131,10 +131,8 @@ class User extends Backend
|
|||||||
$nextChannelId = null;
|
$nextChannelId = null;
|
||||||
if (array_key_exists('channel_id', $data)) {
|
if (array_key_exists('channel_id', $data)) {
|
||||||
$nextChannelId = $data['channel_id'];
|
$nextChannelId = $data['channel_id'];
|
||||||
} elseif (array_key_exists('game_channel_id', $data)) {
|
|
||||||
$nextChannelId = $data['game_channel_id'];
|
|
||||||
} else {
|
} else {
|
||||||
$nextChannelId = $row['channel_id'] ?? $row['game_channel_id'] ?? null;
|
$nextChannelId = $row['channel_id'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($nextUsername) && trim($nextUsername) !== '' && $nextChannelId !== null && $nextChannelId !== '') {
|
if (is_string($nextUsername) && trim($nextUsername) !== '' && $nextChannelId !== null && $nextChannelId !== '') {
|
||||||
@@ -194,7 +192,7 @@ class User extends Backend
|
|||||||
$res = $this->model
|
$res = $this->model
|
||||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
->withJoin($this->withJoinTable, $this->withJoinType)
|
||||||
->with($this->withJoinTable)
|
->with($this->withJoinTable)
|
||||||
->visible(['gameChannel' => ['name'], 'admin' => ['username']])
|
->visible(['channel' => ['name'], 'admin' => ['username']])
|
||||||
->alias($alias)
|
->alias($alias)
|
||||||
->where($where)
|
->where($where)
|
||||||
->order($order)
|
->order($order)
|
||||||
|
|||||||
@@ -27,9 +27,14 @@ class GameUser extends Model
|
|||||||
return is_null($value) ? null : (float)$value;
|
return is_null($value) ? null : (float)$value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function channel(): \think\model\relation\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(\app\common\model\Channel::class, 'channel_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
public function gameChannel(): \think\model\relation\BelongsTo
|
public function gameChannel(): \think\model\relation\BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(\app\common\model\GameChannel::class, 'game_channel_id', 'id');
|
return $this->channel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function admin(): \think\model\relation\BelongsTo
|
public function admin(): \think\model\relation\BelongsTo
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ export default {
|
|||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
mobile: 'Mobile Number',
|
mobile: 'Mobile Number',
|
||||||
|
invite_code: 'Invite code',
|
||||||
|
commission_rate: 'Commission rate(%)',
|
||||||
|
commission_rate_desc_title: 'Admin commission notes',
|
||||||
|
commission_rate_desc_1: 'Admin commission means this admin allocation ratio inside assigned group.',
|
||||||
|
commission_rate_desc_2: 'Current admin commission = current group commission × current admin commission rate.',
|
||||||
|
commission_rate_desc_3: 'Within same group, total admin commission rate cannot exceed 100%; exceed and remaining are returned on validation.',
|
||||||
|
'Please select exactly one group': 'Please select exactly one group',
|
||||||
'Last login': 'Last login',
|
'Last login': 'Last login',
|
||||||
Password: 'Password',
|
Password: 'Password',
|
||||||
'Please leave blank if not modified': 'Please leave blank if you do not modify.',
|
'Please leave blank if not modified': 'Please leave blank if you do not modify.',
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default {
|
|||||||
status: 'status',
|
status: 'status',
|
||||||
'status 0': 'status 0',
|
'status 0': 'status 0',
|
||||||
'status 1': 'status 1',
|
'status 1': 'status 1',
|
||||||
game_channel_id: 'game_channel_id',
|
channel_id: 'channel_id',
|
||||||
gamechannel__name: 'name',
|
channel__name: 'name',
|
||||||
admin_id: 'admin_id',
|
admin_id: 'admin_id',
|
||||||
admin__username: 'username',
|
admin__username: 'username',
|
||||||
create_time: 'create_time',
|
create_time: 'create_time',
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ export default {
|
|||||||
avatar: '头像',
|
avatar: '头像',
|
||||||
email: '电子邮箱',
|
email: '电子邮箱',
|
||||||
mobile: '手机号',
|
mobile: '手机号',
|
||||||
|
invite_code: '邀请码',
|
||||||
|
commission_rate: '分红比(%)',
|
||||||
|
commission_rate_desc_title: '管理员分红说明',
|
||||||
|
commission_rate_desc_1: '管理员分红用于该管理员在所属角色组内的分配比例。',
|
||||||
|
commission_rate_desc_2: '当前管理员分红=当前角色分红×当前管理员分红比例。',
|
||||||
|
commission_rate_desc_3: '同一角色组内,管理员分红比例总和不能超过100%;超额会提示超出值与剩余额度。',
|
||||||
|
'Please select exactly one group': '请选择且仅选择一个角色组',
|
||||||
'Last login': '最后登录',
|
'Last login': '最后登录',
|
||||||
Password: '密码',
|
Password: '密码',
|
||||||
'Please leave blank if not modified': '不修改请留空',
|
'Please leave blank if not modified': '不修改请留空',
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default {
|
|||||||
status: '状态',
|
status: '状态',
|
||||||
'status 0': '禁用',
|
'status 0': '禁用',
|
||||||
'status 1': '启用',
|
'status 1': '启用',
|
||||||
game_channel_id: '所属渠道',
|
channel_id: '所属渠道',
|
||||||
gamechannel__name: '渠道名',
|
channel__name: '渠道名',
|
||||||
admin_id: '所属管理员',
|
admin_id: '所属管理员',
|
||||||
admin__username: '用户名',
|
admin__username: '用户名',
|
||||||
create_time: '创建时间',
|
create_time: '创建时间',
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ optButtons[1].display = (row) => {
|
|||||||
return row.id != adminInfo.id
|
return row.id != adminInfo.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatRatePercent = (_row: any, _column: any, cellValue: number | string | null) => {
|
||||||
|
if (cellValue === null || cellValue === undefined || cellValue === '') return '--'
|
||||||
|
const num = Number(cellValue)
|
||||||
|
if (Number.isNaN(num)) return '--'
|
||||||
|
return `${num.toFixed(2)}%`
|
||||||
|
}
|
||||||
|
|
||||||
const baTable = new baTableClass(
|
const baTable = new baTableClass(
|
||||||
new baTableApi('/admin/auth.Admin/'),
|
new baTableApi('/admin/auth.Admin/'),
|
||||||
{
|
{
|
||||||
@@ -48,7 +55,23 @@ const baTable = new baTableClass(
|
|||||||
{ label: t('Id'), prop: 'id', align: 'center', operator: '=', operatorPlaceholder: t('Id'), width: 70 },
|
{ label: t('Id'), prop: 'id', align: 'center', operator: '=', operatorPlaceholder: t('Id'), width: 70 },
|
||||||
{ label: t('auth.admin.username'), prop: 'username', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
{ label: t('auth.admin.username'), prop: 'username', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||||
{ label: t('auth.admin.nickname'), prop: 'nickname', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
{ label: t('auth.admin.nickname'), prop: 'nickname', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||||
{ label: t('auth.admin.group'), prop: 'group_name_arr', align: 'center', operator: false, render: 'tags' },
|
{
|
||||||
|
label: t('auth.admin.group'),
|
||||||
|
prop: 'group_name_arr',
|
||||||
|
align: 'center',
|
||||||
|
minWidth: 150,
|
||||||
|
operator: false,
|
||||||
|
render: 'tags',
|
||||||
|
},
|
||||||
|
{ label: t('auth.admin.invite_code'), prop: 'invite_code', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||||
|
{
|
||||||
|
label: t('auth.admin.commission_rate'),
|
||||||
|
prop: 'commission_rate',
|
||||||
|
align: 'center',
|
||||||
|
minWidth: 90,
|
||||||
|
operator: 'RANGE',
|
||||||
|
formatter: formatRatePercent,
|
||||||
|
},
|
||||||
{ label: t('auth.admin.avatar'), prop: 'avatar', align: 'center', render: 'image', operator: false },
|
{ label: t('auth.admin.avatar'), prop: 'avatar', align: 'center', render: 'image', operator: false },
|
||||||
{ label: t('auth.admin.email'), prop: 'email', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
{ label: t('auth.admin.email'), prop: 'email', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||||
{ label: t('auth.admin.mobile'), prop: 'mobile', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
{ label: t('auth.admin.mobile'), prop: 'mobile', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') },
|
||||||
|
|||||||
@@ -43,18 +43,43 @@
|
|||||||
/>
|
/>
|
||||||
<FormItem
|
<FormItem
|
||||||
:label="t('auth.admin.group')"
|
:label="t('auth.admin.group')"
|
||||||
v-model="baTable.form.items!.group_arr"
|
v-model="singleGroupValue"
|
||||||
prop="group_arr"
|
prop="group_arr"
|
||||||
type="remoteSelect"
|
type="remoteSelect"
|
||||||
:key="'group-' + baTable.form.items!.id"
|
:key="'group-' + baTable.form.items!.id"
|
||||||
:input-attr="{
|
:input-attr="{
|
||||||
multiple: true,
|
multiple: false,
|
||||||
|
disabled: adminInfo.id == baTable.form.items!.id,
|
||||||
params: { isTree: true, absoluteAuth: adminInfo.id == baTable.form.items!.id ? 0 : 1 },
|
params: { isTree: true, absoluteAuth: adminInfo.id == baTable.form.items!.id ? 0 : 1 },
|
||||||
field: 'name',
|
field: 'name',
|
||||||
remoteUrl: '/admin/auth.Group/index',
|
remoteUrl: '/admin/auth.Group/index',
|
||||||
placeholder: t('Click select'),
|
placeholder: t('Click select'),
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
|
<FormItem
|
||||||
|
:label="t('auth.admin.commission_rate')"
|
||||||
|
v-model="baTable.form.items!.commission_rate"
|
||||||
|
type="number"
|
||||||
|
prop="commission_rate"
|
||||||
|
:input-attr="{ step: 0.01, precision: 2, min: 0, max: 100, disabled: shouldDisableCommissionRate() }"
|
||||||
|
:placeholder="t('Please input field', { field: t('auth.admin.commission_rate') })"
|
||||||
|
/>
|
||||||
|
<el-alert class="commission-rate-alert" :title="t('auth.admin.commission_rate_desc_title')" type="info" :closable="false" show-icon>
|
||||||
|
<template #default>
|
||||||
|
<ul class="commission-rate-desc-list">
|
||||||
|
<li>{{ t('auth.admin.commission_rate_desc_1') }}</li>
|
||||||
|
<li>{{ t('auth.admin.commission_rate_desc_2') }}</li>
|
||||||
|
<li>{{ t('auth.admin.commission_rate_desc_3') }}</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
<FormItem
|
||||||
|
v-if="baTable.form.operate == 'Edit'"
|
||||||
|
:label="t('auth.admin.invite_code')"
|
||||||
|
v-model="baTable.form.items!.invite_code"
|
||||||
|
type="string"
|
||||||
|
:input-attr="{ readonly: true, disabled: true }"
|
||||||
|
/>
|
||||||
<FormItem :label="t('auth.admin.avatar')" type="image" v-model="baTable.form.items!.avatar" />
|
<FormItem :label="t('auth.admin.avatar')" type="image" v-model="baTable.form.items!.avatar" />
|
||||||
<FormItem
|
<FormItem
|
||||||
:label="t('auth.admin.email')"
|
:label="t('auth.admin.email')"
|
||||||
@@ -115,7 +140,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, inject, watch, useTemplateRef } from 'vue'
|
import { computed, reactive, inject, watch, useTemplateRef } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type baTableClass from '/@/utils/baTable'
|
import type baTableClass from '/@/utils/baTable'
|
||||||
import { regularPassword, buildValidatorData } from '/@/utils/validate'
|
import { regularPassword, buildValidatorData } from '/@/utils/validate'
|
||||||
@@ -131,10 +156,69 @@ const baTable = inject('baTable') as baTableClass
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
/** 解析管理员分红比例:与后端 isValidCommissionRate 一致地拒绝非法字符,避免 Number('30,00') 等为 NaN 导致误报 */
|
||||||
|
function parseAdminCommissionRateInput(raw: unknown): { kind: 'empty' } | { kind: 'invalid' } | { kind: 'ok'; value: number } {
|
||||||
|
if (raw === null || raw === undefined || raw === '') {
|
||||||
|
return { kind: 'empty' }
|
||||||
|
}
|
||||||
|
if (typeof raw === 'number') {
|
||||||
|
if (!Number.isFinite(raw)) {
|
||||||
|
return { kind: 'invalid' }
|
||||||
|
}
|
||||||
|
return { kind: 'ok', value: raw }
|
||||||
|
}
|
||||||
|
const s = String(raw).trim()
|
||||||
|
if (s === '') {
|
||||||
|
return { kind: 'empty' }
|
||||||
|
}
|
||||||
|
const normalized = s.replace(',', '.')
|
||||||
|
const n = parseFloat(normalized)
|
||||||
|
if (!Number.isFinite(n)) {
|
||||||
|
return { kind: 'invalid' }
|
||||||
|
}
|
||||||
|
return { kind: 'ok', value: n }
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldDisableCommissionRate = () => {
|
||||||
|
return adminInfo.id == baTable.form.items!.id
|
||||||
|
}
|
||||||
|
const singleGroupValue = computed({
|
||||||
|
get: () => {
|
||||||
|
const group = baTable.form.items?.group_arr
|
||||||
|
if (Array.isArray(group)) {
|
||||||
|
return group.length > 0 ? group[0] : ''
|
||||||
|
}
|
||||||
|
return group ?? ''
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
if (!baTable.form.items) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
baTable.form.items.group_arr = value === '' || value === null || value === undefined ? [] : [value]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||||
username: [buildValidatorData({ name: 'required', title: t('auth.admin.username') }), buildValidatorData({ name: 'account' })],
|
username: [buildValidatorData({ name: 'required', title: t('auth.admin.username') }), buildValidatorData({ name: 'account' })],
|
||||||
nickname: [buildValidatorData({ name: 'required', title: t('auth.admin.nickname') })],
|
nickname: [buildValidatorData({ name: 'required', title: t('auth.admin.nickname') })],
|
||||||
group_arr: [buildValidatorData({ name: 'required', message: t('Please select field', { field: t('auth.admin.group') }) })],
|
group_arr: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (_rule: any, val: unknown, callback: Function) => {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
if (val.length !== 1) {
|
||||||
|
return callback(new Error(t('auth.admin.Please select exactly one group')))
|
||||||
|
}
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
|
if (val === null || val === undefined || val === '') {
|
||||||
|
return callback(new Error(t('Please select field', { field: t('auth.admin.group') })))
|
||||||
|
}
|
||||||
|
return callback()
|
||||||
|
},
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
],
|
||||||
email: [buildValidatorData({ name: 'email', message: t('Please enter the correct field', { field: t('auth.admin.email') }) })],
|
email: [buildValidatorData({ name: 'email', message: t('Please enter the correct field', { field: t('auth.admin.email') }) })],
|
||||||
mobile: [buildValidatorData({ name: 'mobile', message: t('Please enter the correct field', { field: t('auth.admin.mobile') }) })],
|
mobile: [buildValidatorData({ name: 'mobile', message: t('Please enter the correct field', { field: t('auth.admin.mobile') }) })],
|
||||||
password: [
|
password: [
|
||||||
@@ -157,6 +241,26 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
|||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
commission_rate: [
|
||||||
|
{
|
||||||
|
validator: (_rule: unknown, val: unknown, callback: (e?: Error) => void) => {
|
||||||
|
const parsed = parseAdminCommissionRateInput(val)
|
||||||
|
if (parsed.kind === 'empty') {
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
|
if (parsed.kind === 'invalid') {
|
||||||
|
return callback(new Error(t('Please enter the correct field', { field: t('auth.admin.commission_rate') })))
|
||||||
|
}
|
||||||
|
const n = parsed.value
|
||||||
|
const rounded = Math.round(n * 100) / 100
|
||||||
|
if (rounded < -0.000001 || rounded > 100.000001) {
|
||||||
|
return callback(new Error(t('Please enter the correct field', { field: t('auth.admin.commission_rate') })))
|
||||||
|
}
|
||||||
|
return callback()
|
||||||
|
},
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -181,6 +285,14 @@ watch(
|
|||||||
width: 110px;
|
width: 110px;
|
||||||
height: 110px;
|
height: 110px;
|
||||||
}
|
}
|
||||||
|
.commission-rate-alert {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.commission-rate-desc-list {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
.avatar-uploader:hover {
|
.avatar-uploader:hover {
|
||||||
border-color: var(--el-color-primary);
|
border-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ const baTable = new baTableClass(
|
|||||||
replaceValue: { '0': t('game.user.status 0'), '1': t('game.user.status 1') },
|
replaceValue: { '0': t('game.user.status 0'), '1': t('game.user.status 1') },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('game.user.gamechannel__name'),
|
label: t('game.user.channel__name'),
|
||||||
prop: 'gameChannel.name',
|
prop: 'channel.name',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
operatorPlaceholder: t('Fuzzy query'),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
prop="status"
|
prop="status"
|
||||||
:input-attr="{ content: { '0': t('game.user.status 0'), '1': t('game.user.status 1') } }"
|
:input-attr="{ content: { '0': t('game.user.status 0'), '1': t('game.user.status 1') } }"
|
||||||
/>
|
/>
|
||||||
<el-form-item :label="t('game.user.game_channel_id')" prop="admin_id">
|
<el-form-item :label="t('game.user.channel_id')" prop="admin_id">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
v-model="baTable.form.items!.admin_id"
|
v-model="baTable.form.items!.admin_id"
|
||||||
class="w100"
|
class="w100"
|
||||||
@@ -139,7 +139,7 @@ const treeProps = {
|
|||||||
|
|
||||||
const loadChannelAdminTree = async () => {
|
const loadChannelAdminTree = async () => {
|
||||||
const res = await createAxios({
|
const res = await createAxios({
|
||||||
url: '/admin/game.Channel/adminTree',
|
url: '/admin/channel/adminTree',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
const list = (res.data?.list ?? []) as TreeNode[]
|
const list = (res.data?.list ?? []) as TreeNode[]
|
||||||
@@ -166,7 +166,7 @@ const onAdminTreeChange = (val: string | number | null) => {
|
|||||||
const key = typeof val === 'number' ? String(val) : val
|
const key = typeof val === 'number' ? String(val) : val
|
||||||
const channelId = adminIdToChannelId.value[key]
|
const channelId = adminIdToChannelId.value[key]
|
||||||
if (channelId !== undefined) {
|
if (channelId !== undefined) {
|
||||||
baTable.form.items!.game_channel_id = channelId
|
baTable.form.items!.channel_id = channelId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user