移除渠道管理
This commit is contained in:
@@ -17,7 +17,7 @@ class Admin extends Backend
|
|||||||
{
|
{
|
||||||
protected ?object $model = null;
|
protected ?object $model = null;
|
||||||
|
|
||||||
protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip', 'agent_id'];
|
protected array|string $preExcludeFields = ['create_time', 'update_time', 'password', 'salt', 'login_failure', 'last_login_time', 'last_login_ip', 'agent_id', 'agent_api_secret', 'channel_id'];
|
||||||
|
|
||||||
protected array|string $quickSearchField = ['username', 'nickname'];
|
protected array|string $quickSearchField = ['username', 'nickname'];
|
||||||
|
|
||||||
@@ -25,8 +25,6 @@ class Admin extends Backend
|
|||||||
|
|
||||||
protected string $dataLimitField = 'id';
|
protected string $dataLimitField = 'id';
|
||||||
|
|
||||||
protected array $withJoinTable = ['channel'];
|
|
||||||
|
|
||||||
protected function initController(Request $request): ?Response
|
protected function initController(Request $request): ?Response
|
||||||
{
|
{
|
||||||
$this->model = new AdminModel();
|
$this->model = new AdminModel();
|
||||||
@@ -46,8 +44,7 @@ class Admin extends Backend
|
|||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
||||||
$res = $this->model
|
$res = $this->model
|
||||||
->withoutField('login_failure,password,salt')
|
->withoutField('login_failure,password,salt')
|
||||||
->withJoin($this->withJoinTable, $this->withJoinType ?? 'LEFT')
|
->withJoin($this->withJoinTable ?? [], $this->withJoinType ?? 'LEFT')
|
||||||
->visible(['channel' => ['name']])
|
|
||||||
->alias($alias)
|
->alias($alias)
|
||||||
->where($where)
|
->where($where)
|
||||||
->order($order)
|
->order($order)
|
||||||
@@ -81,13 +78,9 @@ class Admin extends Backend
|
|||||||
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile',
|
'mobile' => 'regex:/^1[3-9]\d{9}$/|unique:admin,mobile',
|
||||||
'group_arr' => 'required|array',
|
'group_arr' => 'required|array',
|
||||||
];
|
];
|
||||||
if ($this->auth->isSuperAdmin()) {
|
|
||||||
$rules['channel_id'] = 'required|integer|min:1';
|
|
||||||
}
|
|
||||||
$messages = [
|
$messages = [
|
||||||
'username.regex' => __('Please input correct username'),
|
'username.regex' => __('Please input correct username'),
|
||||||
'password.regex' => __('Please input correct password'),
|
'password.regex' => __('Please input correct password'),
|
||||||
'channel_id.required' => __('Please select channel'),
|
|
||||||
];
|
];
|
||||||
Validator::make($data, $rules, $messages)->validate();
|
Validator::make($data, $rules, $messages)->validate();
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
@@ -95,14 +88,6 @@ class Admin extends Backend
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->auth->isSuperAdmin()) {
|
|
||||||
$currentChannelId = (int) ($this->auth->model->channel_id ?? 0);
|
|
||||||
if ($currentChannelId <= 0) {
|
|
||||||
return $this->error(__('Current admin has no channel bound'));
|
|
||||||
}
|
|
||||||
$data['channel_id'] = $currentChannelId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$passwd = $data['password'] ?? '';
|
$passwd = $data['password'] ?? '';
|
||||||
$data = $this->excludeFields($data);
|
$data = $this->excludeFields($data);
|
||||||
$result = false;
|
$result = false;
|
||||||
@@ -115,7 +100,12 @@ class Admin extends Backend
|
|||||||
$result = $this->model->save($data);
|
$result = $this->model->save($data);
|
||||||
if ($result !== false) {
|
if ($result !== false) {
|
||||||
$agentId = strtolower(md5($this->model->username . $this->model->id));
|
$agentId = strtolower(md5($this->model->username . $this->model->id));
|
||||||
$this->model->where('id', $this->model->id)->update(['agent_id' => $agentId]);
|
$agentSecret = strtoupper(md5($this->model->username . $this->model->id));
|
||||||
|
// 使用原生 SQL,避免 ThinkORM 按当前表结构校验字段时因未迁移缺少 agent_api_secret 列而报错
|
||||||
|
Db::execute(
|
||||||
|
'UPDATE `admin` SET `agent_id` = ?, `agent_api_secret` = ? WHERE `id` = ?',
|
||||||
|
[$agentId, $agentSecret, $this->model->id]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!empty($data['group_arr'])) {
|
if (!empty($data['group_arr'])) {
|
||||||
$groupAccess = [];
|
$groupAccess = [];
|
||||||
@@ -306,6 +296,39 @@ class Admin extends Backend
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去掉已废弃的渠道字段,避免组合搜索生成对不存在列的条件
|
||||||
|
*/
|
||||||
|
protected function filterSearchArray(array $search): array
|
||||||
|
{
|
||||||
|
return array_values(array_filter($search, static function ($item) {
|
||||||
|
if (!is_array($item) || !isset($item['field'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$f = (string) $item['field'];
|
||||||
|
if ($f === 'channel_id' || str_ends_with($f, '.channel_id')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($f === 'channel.name' || str_starts_with($f, 'channel.')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queryOrderBuilder(): array
|
||||||
|
{
|
||||||
|
$order = parent::queryOrderBuilder();
|
||||||
|
foreach (array_keys($order) as $key) {
|
||||||
|
if ($key === 'channel_id' || (is_string($key) && str_contains($key, 'channel.'))) {
|
||||||
|
unset($order[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
}
|
||||||
|
|
||||||
private function checkGroupAuth(array $groups): ?Response
|
private function checkGroupAuth(array $groups): ?Response
|
||||||
{
|
{
|
||||||
if ($this->auth->isSuperAdmin()) {
|
if ($this->auth->isSuperAdmin()) {
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace app\admin\controller\channel;
|
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
use app\common\controller\Backend;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道管理
|
|
||||||
*/
|
|
||||||
class Manage extends Backend
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* ChannelManage模型对象
|
|
||||||
* @var object|null
|
|
||||||
* @phpstan-var \app\common\model\ChannelManage|null
|
|
||||||
*/
|
|
||||||
protected ?object $model = null;
|
|
||||||
|
|
||||||
protected array|string $preExcludeFields = ['id', 'create_time', 'update_time', 'secret'];
|
|
||||||
|
|
||||||
protected array $withJoinTable = ['admin'];
|
|
||||||
|
|
||||||
protected string|array $quickSearchField = ['id'];
|
|
||||||
|
|
||||||
protected bool $autoFillAdminId = true;
|
|
||||||
|
|
||||||
public function initialize(): void
|
|
||||||
{
|
|
||||||
parent::initialize();
|
|
||||||
$this->model = new \app\common\model\ChannelManage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查看
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
|
||||||
public function index(\Webman\Http\Request $request): \support\Response
|
|
||||||
{
|
|
||||||
$response = $this->initializeBackend($request);
|
|
||||||
if ($response !== null) {
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->get('select') || $request->post('select')) {
|
|
||||||
return $this->select($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. withJoin 不可使用 alias 方法设置表别名,别名将自动使用关联模型名称(小写下划线命名规则)
|
|
||||||
* 2. 以下的别名设置了主表别名,同时便于拼接查询参数等
|
|
||||||
* 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理
|
|
||||||
*/
|
|
||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
|
||||||
$res = $this->model
|
|
||||||
->withJoin($this->withJoinTable, $this->withJoinType)
|
|
||||||
->visible(['admin' => ['username']])
|
|
||||||
->alias($alias)
|
|
||||||
->where($where)
|
|
||||||
->order($order)
|
|
||||||
->paginate($limit);
|
|
||||||
|
|
||||||
return $this->success('', [
|
|
||||||
'list' => $res->items(),
|
|
||||||
'total' => $res->total(),
|
|
||||||
'remark' => get_route_remark(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 白名单(页面按钮规则,用于菜单规则中配置按钮权限)
|
|
||||||
* 实际编辑通过 edit 接口提交 ip_white 字段
|
|
||||||
*/
|
|
||||||
public function whitelist(\Webman\Http\Request $request): \support\Response
|
|
||||||
{
|
|
||||||
$response = $this->initializeBackend($request);
|
|
||||||
if ($response !== null) {
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
return $this->success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道下拉选择(供 remoteSelect 使用)
|
|
||||||
*/
|
|
||||||
public function select(\Webman\Http\Request $request): \support\Response
|
|
||||||
{
|
|
||||||
$response = $this->initializeBackend($request);
|
|
||||||
if ($response !== null) {
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
list($where, $alias, $limit, $order) = $this->queryBuilder();
|
|
||||||
$res = $this->model
|
|
||||||
->field('id,name,title')
|
|
||||||
->alias($alias)
|
|
||||||
->where($where)
|
|
||||||
->order($order)
|
|
||||||
->paginate($limit);
|
|
||||||
return $this->success('', [
|
|
||||||
'list' => $res->items(),
|
|
||||||
'total' => $res->total(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add、edit、del、sortable 已由父类 Backend 实现,无需重写即可直接使用
|
|
||||||
* 若需重写,请确保调用 initializeBackend($request) 并传入 Request 参数
|
|
||||||
* 若模型有 admin_id 字段需自动填充,可设置 protected bool $autoFillAdminId = true
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ class AdminInfo extends Backend
|
|||||||
{
|
{
|
||||||
protected ?object $model = null;
|
protected ?object $model = null;
|
||||||
|
|
||||||
protected array|string $preExcludeFields = ['username', 'last_login_time', 'password', 'salt', 'status'];
|
protected array|string $preExcludeFields = ['username', 'last_login_time', 'password', 'salt', 'status', 'channel_id', 'agent_id', 'agent_api_secret'];
|
||||||
protected array $authAllowFields = ['id', 'username', 'nickname', 'avatar', 'email', 'mobile', 'motto', 'last_login_time'];
|
protected array $authAllowFields = ['id', 'username', 'nickname', 'avatar', 'email', 'mobile', 'motto', 'last_login_time'];
|
||||||
|
|
||||||
protected function initController(Request $request): ?Response
|
protected function initController(Request $request): ?Response
|
||||||
|
|||||||
@@ -2,6 +2,4 @@
|
|||||||
return [
|
return [
|
||||||
'Group Name Arr' => 'Administrator Grouping ',
|
'Group Name Arr' => 'Administrator Grouping ',
|
||||||
'Please use another administrator account to disable the current account!' => 'Disable the current account, please use another administrator account!',
|
'Please use another administrator account to disable the current account!' => 'Disable the current account, please use another administrator account!',
|
||||||
'Please select channel' => 'Please select channel',
|
|
||||||
'Current admin has no channel bound' => 'Current admin has no channel bound',
|
|
||||||
];
|
];
|
||||||
@@ -3,6 +3,4 @@ return [
|
|||||||
'Group Name Arr' => '管理员分组',
|
'Group Name Arr' => '管理员分组',
|
||||||
'Please use another administrator account to disable the current account!' => '请使用另外的管理员账户禁用当前账户!',
|
'Please use another administrator account to disable the current account!' => '请使用另外的管理员账户禁用当前账户!',
|
||||||
'You have no permission to add an administrator to this group!' => '您没有权限向此分组添加管理员!',
|
'You have no permission to add an administrator to this group!' => '您没有权限向此分组添加管理员!',
|
||||||
'Please select channel' => '请选择渠道',
|
|
||||||
'Current admin has no channel bound' => '当前管理员未绑定渠道',
|
|
||||||
];
|
];
|
||||||
@@ -33,7 +33,7 @@ class Auth extends \ba\Auth
|
|||||||
protected string $refreshToken = '';
|
protected string $refreshToken = '';
|
||||||
protected int $keepTime = 86400;
|
protected int $keepTime = 86400;
|
||||||
protected int $refreshTokenKeepTime = 2592000;
|
protected int $refreshTokenKeepTime = 2592000;
|
||||||
protected array $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time', 'channel_id'];
|
protected array $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time'];
|
||||||
|
|
||||||
public function __construct(array $config = [])
|
public function __construct(array $config = [])
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,13 +21,23 @@ use support\think\Db;
|
|||||||
* @property string $password 密码密文
|
* @property string $password 密码密文
|
||||||
* @property string $salt 密码盐
|
* @property string $salt 密码盐
|
||||||
* @property string $status 状态:enable=启用,disable=禁用
|
* @property string $status 状态:enable=启用,disable=禁用
|
||||||
* @property string $agent_id 代理ID(关联渠道)
|
* @property string $agent_id 代理 ID(API 鉴权)
|
||||||
* @property int $channel_id 渠道ID
|
* @property string $agent_api_secret Agent API 密钥
|
||||||
*/
|
*/
|
||||||
class Admin extends Model
|
class Admin extends Model
|
||||||
{
|
{
|
||||||
use TimestampInteger;
|
use TimestampInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已移除的 channel_id 等若仍被旧请求/缓存传入,禁止参与读写
|
||||||
|
*/
|
||||||
|
protected function getOptions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'disuse' => ['channel_id'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected string $table = 'admin';
|
protected string $table = 'admin';
|
||||||
protected string $pk = 'id';
|
protected string $pk = 'id';
|
||||||
protected bool $autoWriteTimestamp = true;
|
protected bool $autoWriteTimestamp = true;
|
||||||
@@ -66,12 +76,4 @@ class Admin extends Model
|
|||||||
{
|
{
|
||||||
return $this->where(['id' => $uid])->update(['password' => hash_password($newPassword), 'salt' => '']);
|
return $this->where(['id' => $uid])->update(['password' => hash_password($newPassword), 'salt' => '']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 关联渠道
|
|
||||||
*/
|
|
||||||
public function channel(): \think\model\relation\BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(\app\common\model\ChannelManage::class, 'channel_id', 'id');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use app\common\controller\Api;
|
|||||||
use app\common\facade\Token;
|
use app\common\facade\Token;
|
||||||
use app\common\library\Auth as UserAuth;
|
use app\common\library\Auth as UserAuth;
|
||||||
use app\common\library\AgentJwt;
|
use app\common\library\AgentJwt;
|
||||||
use app\common\model\ChannelManage;
|
|
||||||
use app\common\model\MallPlayxUserAsset;
|
use app\common\model\MallPlayxUserAsset;
|
||||||
use app\admin\model\Admin;
|
use app\admin\model\Admin;
|
||||||
use Webman\Http\Request;
|
use Webman\Http\Request;
|
||||||
@@ -72,17 +71,12 @@ class Auth extends Api
|
|||||||
return $this->error(__('Agent not found'));
|
return $this->error(__('Agent not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$channelId = intval($admin->channel_id ?? 0);
|
$apiSecret = strval($admin->agent_api_secret ?? '');
|
||||||
if ($channelId <= 0) {
|
if ($apiSecret === '') {
|
||||||
return $this->error(__('Agent not found'));
|
return $this->error(__('Agent not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$channel = ChannelManage::where('id', $channelId)->find();
|
if ($apiSecret !== $secret) {
|
||||||
if (!$channel || $channel->secret === '') {
|
|
||||||
return $this->error(__('Agent not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($channel->secret !== $secret) {
|
|
||||||
return $this->error(__('Invalid agent or secret'));
|
return $this->error(__('Invalid agent or secret'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +87,8 @@ class Auth extends Api
|
|||||||
|
|
||||||
$expire = intval(config('buildadmin.agent_auth.token_expire', 86400));
|
$expire = intval(config('buildadmin.agent_auth.token_expire', 86400));
|
||||||
$payload = [
|
$payload = [
|
||||||
'agent_id' => $agentId,
|
'agent_id' => $agentId,
|
||||||
'channel_id' => $channel->id,
|
'admin_id' => $admin->id,
|
||||||
'admin_id' => $admin->id,
|
|
||||||
];
|
];
|
||||||
$authtoken = AgentJwt::encode($payload, $expire);
|
$authtoken = AgentJwt::encode($payload, $expire);
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ class Backend extends Api
|
|||||||
$limit = is_numeric($limit) ? intval($limit) : 10;
|
$limit = is_numeric($limit) ? intval($limit) : 10;
|
||||||
$search = $this->request->get('search', []);
|
$search = $this->request->get('search', []);
|
||||||
$search = is_array($search) ? $search : [];
|
$search = is_array($search) ? $search : [];
|
||||||
|
$search = $this->filterSearchArray($search);
|
||||||
$initKey = $this->request->get('initKey', $pk);
|
$initKey = $this->request->get('initKey', $pk);
|
||||||
$initValue = $this->request->get('initValue', '');
|
$initValue = $this->request->get('initValue', '');
|
||||||
$initOperator = $this->request->get('initOperator', 'in');
|
$initOperator = $this->request->get('initOperator', 'in');
|
||||||
@@ -352,6 +353,14 @@ class Backend extends Api
|
|||||||
return [$where, $alias, $limit, $this->queryOrderBuilder()];
|
return [$where, $alias, $limit, $this->queryOrderBuilder()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合搜索条件过滤(子类可覆盖,例如去掉已删除的数据库字段)
|
||||||
|
*/
|
||||||
|
protected function filterSearchArray(array $search): array
|
||||||
|
{
|
||||||
|
return $search;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询的排序参数构建器
|
* 查询的排序参数构建器
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class AgentJwt
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成 JWT authtoken
|
* 生成 JWT authtoken
|
||||||
* @param array $payload agent_id, channel_id, admin_id 等
|
* @param array $payload agent_id、admin_id 等
|
||||||
* @param int $expire 有效期(秒)
|
* @param int $expire 有效期(秒)
|
||||||
*/
|
*/
|
||||||
public static function encode(array $payload, int $expire = 86400): string
|
public static function encode(array $payload, int $expire = 86400): string
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace app\common\model;
|
|
||||||
|
|
||||||
use support\think\Model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChannelManage
|
|
||||||
*/
|
|
||||||
class ChannelManage extends Model
|
|
||||||
{
|
|
||||||
// 表名
|
|
||||||
protected $name = 'channel_manage';
|
|
||||||
|
|
||||||
// 自动写入时间戳字段
|
|
||||||
protected $autoWriteTimestamp = true;
|
|
||||||
|
|
||||||
// 字段类型转换
|
|
||||||
protected $type = [
|
|
||||||
'create_time' => 'integer',
|
|
||||||
'update_time' => 'integer',
|
|
||||||
'ip_white' => 'json',
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 IP 白名单,统一返回字符串数组格式
|
|
||||||
* 兼容:["127.0.0.1"]、[{"value":"127.0.0.1"}]、[{"127.0.0.1":""}]
|
|
||||||
*/
|
|
||||||
public function getipWhiteAttr($value): array
|
|
||||||
{
|
|
||||||
$arr = is_array($value) ? $value : (!$value ? [] : json_decode($value, true));
|
|
||||||
if (!is_array($arr)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$result = [];
|
|
||||||
foreach ($arr as $item) {
|
|
||||||
if (is_string($item)) {
|
|
||||||
$result[] = $item;
|
|
||||||
} elseif (is_array($item)) {
|
|
||||||
if (isset($item['value'])) {
|
|
||||||
$result[] = $item['value'];
|
|
||||||
} else {
|
|
||||||
$key = array_key_first($item);
|
|
||||||
if ($key !== null && $key !== '') {
|
|
||||||
$result[] = $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array_values(array_filter($result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写入 IP 白名单,存储格式 ["127.0.0.1","192.168.1.1"]
|
|
||||||
*/
|
|
||||||
public function setipWhiteAttr($value): array
|
|
||||||
{
|
|
||||||
$arr = is_array($value) ? $value : [];
|
|
||||||
$result = [];
|
|
||||||
foreach ($arr as $ip) {
|
|
||||||
$ip = is_string($ip) ? trim($ip) : '';
|
|
||||||
if ($ip !== '') {
|
|
||||||
$result[] = $ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array_values($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建时自动生成密钥:strtoupper(md5(name+id))
|
|
||||||
*/
|
|
||||||
protected static function onAfterInsert($model): void
|
|
||||||
{
|
|
||||||
$pk = $model->getPk();
|
|
||||||
$secret = strtoupper(md5($model->name . $model->$pk));
|
|
||||||
$model->where($pk, $model->$pk)->update(['secret' => $secret]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function admin(): \think\model\relation\BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(\app\admin\model\Admin::class, 'admin_id', 'id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace app\common\validate;
|
|
||||||
|
|
||||||
use think\Validate;
|
|
||||||
|
|
||||||
class ChannelManage extends Validate
|
|
||||||
{
|
|
||||||
protected $failException = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证规则
|
|
||||||
*/
|
|
||||||
protected $rule = [
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提示消息
|
|
||||||
*/
|
|
||||||
protected $message = [
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证场景
|
|
||||||
*/
|
|
||||||
protected $scene = [
|
|
||||||
'add' => [],
|
|
||||||
'edit' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -167,7 +167,7 @@ if (!function_exists('get_auth_token')) {
|
|||||||
|
|
||||||
if (!function_exists('get_agent_jwt_payload')) {
|
if (!function_exists('get_agent_jwt_payload')) {
|
||||||
/**
|
/**
|
||||||
* 解析 Agent JWT authtoken,返回 payload(agent_id、channel_id、admin_id 等)
|
* 解析 Agent JWT authtoken,返回 payload(agent_id、admin_id 等)
|
||||||
* @param string $token authtoken
|
* @param string $token authtoken
|
||||||
* @return array 成功返回 payload,失败返回空数组
|
* @return array 成功返回 payload,失败返回空数组
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
nickname: 'Nickname',
|
nickname: 'Nickname',
|
||||||
channel_id: 'Channel',
|
|
||||||
channel_name: 'Channel name',
|
|
||||||
'Please select channel': 'Please select channel',
|
|
||||||
group: 'Group',
|
group: 'Group',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
export default {
|
|
||||||
id: 'id',
|
|
||||||
name: 'name',
|
|
||||||
ip_white: 'ip_white',
|
|
||||||
ip_placeholder: 'Please enter IP address',
|
|
||||||
whitelist: 'Whitelist',
|
|
||||||
title: 'title',
|
|
||||||
remark: 'remark',
|
|
||||||
admin_id: 'admin_id',
|
|
||||||
admin__username: 'username',
|
|
||||||
secret: 'secret',
|
|
||||||
create_time: 'create_time',
|
|
||||||
update_time: 'update_time',
|
|
||||||
'quick Search Fields': 'id',
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
nickname: '昵称',
|
nickname: '昵称',
|
||||||
channel_id: '渠道',
|
|
||||||
channel_name: '渠道名称',
|
|
||||||
'Please select channel': '请选择渠道',
|
|
||||||
group: '角色组',
|
group: '角色组',
|
||||||
avatar: '头像',
|
avatar: '头像',
|
||||||
email: '电子邮箱',
|
email: '电子邮箱',
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
export default {
|
|
||||||
id: 'ID',
|
|
||||||
name: '渠道名',
|
|
||||||
ip_white: 'IP白名单',
|
|
||||||
ip_placeholder: '请输入IP地址',
|
|
||||||
whitelist: '白名单',
|
|
||||||
title: '标题',
|
|
||||||
remark: '备注',
|
|
||||||
admin_id: '管理员',
|
|
||||||
admin__username: '用户名',
|
|
||||||
secret: '密钥',
|
|
||||||
create_time: '创建时间',
|
|
||||||
update_time: '修改时间',
|
|
||||||
'quick Search Fields': 'ID',
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ export const useAdminInfo = defineStore('adminInfo', {
|
|||||||
token: '',
|
token: '',
|
||||||
refresh_token: '',
|
refresh_token: '',
|
||||||
super: false,
|
super: false,
|
||||||
channel_id: 0,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|||||||
@@ -113,8 +113,6 @@ export interface AdminInfo {
|
|||||||
refresh_token: string
|
refresh_token: string
|
||||||
// 是否是 superAdmin,用于判定是否显示终端按钮等,不做任何权限判断
|
// 是否是 superAdmin,用于判定是否显示终端按钮等,不做任何权限判断
|
||||||
super: boolean
|
super: boolean
|
||||||
// 渠道ID(创建子管理员时默认绑定)
|
|
||||||
channel_id?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ const baTable = new baTableClass(
|
|||||||
operator: 'RANGE',
|
operator: 'RANGE',
|
||||||
width: 160,
|
width: 160,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: t('auth.admin.channel_name'),
|
|
||||||
prop: 'channel.name',
|
|
||||||
align: 'center',
|
|
||||||
minWidth: 120,
|
|
||||||
operator: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: t('auth.admin.agent_id'),
|
label: t('auth.admin.agent_id'),
|
||||||
prop: 'agent_id',
|
prop: 'agent_id',
|
||||||
|
|||||||
@@ -41,19 +41,6 @@
|
|||||||
prop="nickname"
|
prop="nickname"
|
||||||
:placeholder="t('Please input field', { field: t('auth.admin.nickname') })"
|
:placeholder="t('Please input field', { field: t('auth.admin.nickname') })"
|
||||||
/>
|
/>
|
||||||
<FormItem
|
|
||||||
v-if="baTable.form.operate === 'Add' && adminInfo.super"
|
|
||||||
:label="t('auth.admin.channel_id')"
|
|
||||||
v-model="baTable.form.items!.channel_id"
|
|
||||||
prop="channel_id"
|
|
||||||
type="remoteSelect"
|
|
||||||
:input-attr="{
|
|
||||||
pk: 'id',
|
|
||||||
field: 'name',
|
|
||||||
remoteUrl: '/admin/channel.Manage/index',
|
|
||||||
placeholder: t('auth.admin.Please select channel'),
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<FormItem
|
<FormItem
|
||||||
:label="t('auth.admin.group')"
|
:label="t('auth.admin.group')"
|
||||||
v-model="baTable.form.items!.group_arr"
|
v-model="baTable.form.items!.group_arr"
|
||||||
@@ -147,17 +134,6 @@ const { t } = useI18n()
|
|||||||
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') })],
|
||||||
channel_id: [
|
|
||||||
{
|
|
||||||
validator: (_rule: any, val: any, callback: Function) => {
|
|
||||||
if (baTable.form.operate === 'Add' && adminInfo.super && !val) {
|
|
||||||
return callback(new Error(t('auth.admin.Please select channel')))
|
|
||||||
}
|
|
||||||
return callback()
|
|
||||||
},
|
|
||||||
trigger: 'change',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
group_arr: [buildValidatorData({ name: 'required', message: t('Please select field', { field: t('auth.admin.group') }) })],
|
group_arr: [buildValidatorData({ name: 'required', message: t('Please select field', { field: t('auth.admin.group') }) })],
|
||||||
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') }) })],
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
<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('channel.manage.quick Search Fields') })"
|
|
||||||
></TableHeader>
|
|
||||||
|
|
||||||
<!-- 表格 -->
|
|
||||||
<!-- 表格列有多种自定义渲染方式,比如自定义组件、具名插槽等,参见文档 -->
|
|
||||||
<!-- 要使用 el-table 组件原有的属性,直接加在 Table 标签上即可 -->
|
|
||||||
<Table ref="tableRef"></Table>
|
|
||||||
|
|
||||||
<!-- 表单 -->
|
|
||||||
<PopupForm />
|
|
||||||
<!-- 白名单弹窗 -->
|
|
||||||
<WhitelistPopup v-model:row="whitelistRow" @saved="baTable.getData()" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, provide, ref, useTemplateRef } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import PopupForm from './popupForm.vue'
|
|
||||||
import WhitelistPopup from './whitelistPopup.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: 'channel/manage',
|
|
||||||
})
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
const tableRef = useTemplateRef('tableRef')
|
|
||||||
const whitelistRow = ref<TableRow | null>(null)
|
|
||||||
|
|
||||||
function openWhitelistDialog(row: TableRow) {
|
|
||||||
whitelistRow.value = row
|
|
||||||
}
|
|
||||||
|
|
||||||
const optButtons: OptButton[] = [
|
|
||||||
...defaultOptButtons(['edit', 'delete']),
|
|
||||||
{
|
|
||||||
render: 'tipButton',
|
|
||||||
name: 'whitelist',
|
|
||||||
title: 'channel.manage.whitelist',
|
|
||||||
text: '',
|
|
||||||
type: 'success',
|
|
||||||
icon: 'fa fa-list',
|
|
||||||
class: 'table-row-whitelist',
|
|
||||||
disabledTip: false,
|
|
||||||
display: (_row, _field) => baTable.auth('whitelist'),
|
|
||||||
click: (row: TableRow) => openWhitelistDialog(row),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* baTable 内包含了表格的所有数据且数据具备响应性,然后通过 provide 注入给了后代组件
|
|
||||||
*/
|
|
||||||
const baTable = new baTableClass(
|
|
||||||
new baTableApi('/admin/channel.Manage/'),
|
|
||||||
{
|
|
||||||
pk: 'id',
|
|
||||||
column: [
|
|
||||||
{ type: 'selection', align: 'center', operator: false },
|
|
||||||
{ label: t('channel.manage.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
|
|
||||||
{
|
|
||||||
label: t('channel.manage.name'),
|
|
||||||
prop: 'name',
|
|
||||||
align: 'center',
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
sortable: false,
|
|
||||||
operator: 'LIKE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.title'),
|
|
||||||
prop: 'title',
|
|
||||||
align: 'center',
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
sortable: false,
|
|
||||||
operator: 'LIKE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.admin__username'),
|
|
||||||
prop: 'admin.username',
|
|
||||||
align: 'center',
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
render: 'tags',
|
|
||||||
operator: 'LIKE',
|
|
||||||
comSearchRender: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.secret'),
|
|
||||||
prop: 'secret',
|
|
||||||
align: 'center',
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
sortable: false,
|
|
||||||
operator: 'LIKE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.remark'),
|
|
||||||
prop: 'remark',
|
|
||||||
align: 'center',
|
|
||||||
minWidth: '100',
|
|
||||||
showOverflowTooltip: true,
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
sortable: false,
|
|
||||||
operator: 'LIKE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.ip_white'),
|
|
||||||
prop: 'ip_white',
|
|
||||||
align: 'center',
|
|
||||||
minWidth: '120',
|
|
||||||
render: 'tags',
|
|
||||||
operatorPlaceholder: t('Fuzzy query'),
|
|
||||||
sortable: false,
|
|
||||||
operator: 'LIKE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.create_time'),
|
|
||||||
prop: 'create_time',
|
|
||||||
align: 'center',
|
|
||||||
render: 'datetime',
|
|
||||||
operator: 'RANGE',
|
|
||||||
comSearchRender: 'datetime',
|
|
||||||
sortable: 'custom',
|
|
||||||
width: 160,
|
|
||||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('channel.manage.update_time'),
|
|
||||||
prop: 'update_time',
|
|
||||||
align: 'center',
|
|
||||||
render: 'datetime',
|
|
||||||
operator: 'RANGE',
|
|
||||||
comSearchRender: 'datetime',
|
|
||||||
sortable: 'custom',
|
|
||||||
width: 160,
|
|
||||||
timeFormat: 'yyyy-mm-dd hh:MM:ss',
|
|
||||||
},
|
|
||||||
{ label: t('Operate'), align: 'center', width: 130, render: 'buttons', buttons: optButtons, operator: false },
|
|
||||||
],
|
|
||||||
dblClickNotEditColumn: [undefined],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultItems: { ip_white: [] },
|
|
||||||
before: {
|
|
||||||
onSubmit: () => {
|
|
||||||
const items = baTable.form.items
|
|
||||||
if (!items) return
|
|
||||||
// 从 popupForm 的 ipWhiteList 同步到 form.items(避免 watch 循环)
|
|
||||||
const ipWhiteRef = baTable.form.extend?.ipWhiteListRef
|
|
||||||
if (ipWhiteRef?.value) {
|
|
||||||
items.ip_white = ipWhiteRef.value.filter((ip: string) => ip && String(ip).trim() !== '')
|
|
||||||
} else if (Array.isArray(items.ip_white)) {
|
|
||||||
items.ip_white = items.ip_white.filter((ip: string) => ip && String(ip).trim() !== '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
provide('baTable', baTable)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
baTable.table.ref = tableRef.value
|
|
||||||
baTable.mount()
|
|
||||||
baTable.getData()?.then(() => {
|
|
||||||
baTable.initSort()
|
|
||||||
baTable.dragSort()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- 对话框表单 -->
|
|
||||||
<!-- 建议使用 Prettier 格式化代码 -->
|
|
||||||
<!-- el-form 内可以混用 el-form-item、FormItem、ba-input 等输入组件 -->
|
|
||||||
<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('channel.manage.name')"
|
|
||||||
type="string"
|
|
||||||
v-model="baTable.form.items!.name"
|
|
||||||
prop="name"
|
|
||||||
:placeholder="t('Please input field', { field: t('channel.manage.name') })"
|
|
||||||
/>
|
|
||||||
<el-form-item :label="t('channel.manage.ip_white')" prop="ip_white">
|
|
||||||
<div class="ba-ip-white-list">
|
|
||||||
<div class="ba-ip-white-item" v-for="(ip, idx) in ipWhiteList" :key="idx">
|
|
||||||
<el-input v-model="ipWhiteList[idx]" :placeholder="t('channel.manage.ip_placeholder')" clearable />
|
|
||||||
<el-button @click="onDelIpWhite(idx)" size="small" icon="el-icon-Delete" circle />
|
|
||||||
</div>
|
|
||||||
<el-button v-blur class="ba-add-ip-white" @click="onAddIpWhite" icon="el-icon-Plus">{{ t('Add') }}</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<FormItem
|
|
||||||
:label="t('channel.manage.title')"
|
|
||||||
type="string"
|
|
||||||
v-model="baTable.form.items!.title"
|
|
||||||
prop="title"
|
|
||||||
:placeholder="t('Please input field', { field: t('channel.manage.title') })"
|
|
||||||
/>
|
|
||||||
<FormItem
|
|
||||||
:label="t('channel.manage.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('channel.manage.remark') })"
|
|
||||||
/>
|
|
||||||
<FormItem
|
|
||||||
:label="t('channel.manage.admin_id')"
|
|
||||||
type="remoteSelect"
|
|
||||||
v-model="baTable.form.items!.admin_id"
|
|
||||||
prop="admin_id"
|
|
||||||
:input-attr="{ pk: 'admin.id', field: 'username', remoteUrl: '/admin/auth.Admin/index' }"
|
|
||||||
:placeholder="t('Please select field', { field: t('channel.manage.admin_id') })"
|
|
||||||
/>
|
|
||||||
</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, ref, useTemplateRef, watch } 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'
|
|
||||||
import { buildValidatorData } from '/@/utils/validate'
|
|
||||||
|
|
||||||
const config = useConfig()
|
|
||||||
const formRef = useTemplateRef('formRef')
|
|
||||||
const baTable = inject('baTable') as baTableClass
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
/** 将后端数据转为 IP 字符串数组(兼容旧格式 [{key,value}] 和新格式 [ip]) */
|
|
||||||
function normalizeIpWhite(val: unknown): string[] {
|
|
||||||
if (!val || !Array.isArray(val)) return []
|
|
||||||
return val.map((item) => (typeof item === 'string' ? item : (item?.value ?? '')))
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipWhiteList = ref<string[]>([])
|
|
||||||
|
|
||||||
// 仅当表单加载时从 form.items 同步到 ipWhiteList,避免双向 watch 导致循环更新
|
|
||||||
watch(
|
|
||||||
() => baTable.form.items?.ip_white,
|
|
||||||
(val) => {
|
|
||||||
ipWhiteList.value = normalizeIpWhite(val)
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAddIpWhite = () => {
|
|
||||||
ipWhiteList.value.push('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDelIpWhite = (idx: number) => {
|
|
||||||
ipWhiteList.value.splice(idx, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将 ipWhiteList 暴露给 baTable,供提交时同步
|
|
||||||
if (!baTable.form.extend) baTable.form.extend = {}
|
|
||||||
baTable.form.extend.ipWhiteListRef = ipWhiteList
|
|
||||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
|
||||||
name: [buildValidatorData({ name: 'required', title: t('channel.manage.name') })],
|
|
||||||
title: [buildValidatorData({ name: 'required', title: t('channel.manage.title') })],
|
|
||||||
admin_id: [buildValidatorData({ name: 'required', title: t('channel.manage.admin_id') })],
|
|
||||||
create_time: [buildValidatorData({ name: 'date', title: t('channel.manage.create_time') })],
|
|
||||||
update_time: [buildValidatorData({ name: 'date', title: t('channel.manage.update_time') })],
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.ba-ip-white-list {
|
|
||||||
.ba-ip-white-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
.el-input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ba-add-ip-white {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="!!row" :title="t('channel.manage.whitelist')" @close="close">
|
|
||||||
<el-scrollbar v-loading="loading" class="ba-table-form-scrollbar">
|
|
||||||
<div class="ba-operate-form">
|
|
||||||
<div class="ba-ip-white-list">
|
|
||||||
<div class="ba-ip-white-item" v-for="(ip, idx) in ipWhiteList" :key="idx">
|
|
||||||
<el-input v-model="ipWhiteList[idx]" :placeholder="t('channel.manage.ip_placeholder')" clearable />
|
|
||||||
<el-button @click="onDelIpWhite(idx)" size="small" icon="el-icon-Delete" circle />
|
|
||||||
</div>
|
|
||||||
<el-button v-blur class="ba-add-ip-white" @click="onAddIpWhite" icon="el-icon-Plus">{{ t('Add') }}</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-scrollbar>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="close">{{ t('Cancel') }}</el-button>
|
|
||||||
<el-button v-blur :loading="submitLoading" @click="onSave" type="primary">{{ t('Save') }}</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { baTableApi } from '/@/api/common'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
row: TableRow | null
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:row', val: TableRow | null): void
|
|
||||||
(e: 'saved'): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const api = new baTableApi('/admin/channel.Manage/')
|
|
||||||
const loading = ref(false)
|
|
||||||
const submitLoading = ref(false)
|
|
||||||
|
|
||||||
/** 将后端数据转为 IP 字符串数组(兼容旧格式 [{key,value}] 和新格式 [ip]) */
|
|
||||||
function normalizeIpWhite(val: unknown): string[] {
|
|
||||||
if (!val || !Array.isArray(val)) return []
|
|
||||||
return val.map((item) => (typeof item === 'string' ? item : (item?.value ?? '')))
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipWhiteList = ref<string[]>([])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.row,
|
|
||||||
(val) => {
|
|
||||||
ipWhiteList.value = val ? normalizeIpWhite(val.ip_white) : []
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAddIpWhite = () => {
|
|
||||||
ipWhiteList.value.push('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDelIpWhite = (idx: number) => {
|
|
||||||
ipWhiteList.value.splice(idx, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
emit('update:row', null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSave = async () => {
|
|
||||||
if (!props.row) return
|
|
||||||
const ips = ipWhiteList.value.filter((ip) => ip.trim() !== '')
|
|
||||||
submitLoading.value = true
|
|
||||||
try {
|
|
||||||
await api.postData('edit', {
|
|
||||||
id: props.row.id,
|
|
||||||
ip_white: ips,
|
|
||||||
})
|
|
||||||
emit('saved')
|
|
||||||
close()
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.ba-ip-white-list {
|
|
||||||
.ba-ip-white-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
.el-input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ba-add-ip-white {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user