Files
links/application/admin/controller/split/Number.php
T

436 lines
15 KiB
PHP
Raw Normal View History

2026-06-04 14:15:12 +08:00
<?php
declare(strict_types=1);
namespace app\admin\controller\split;
use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Number as NumberModel;
use app\common\controller\Backend;
use think\Db;
use think\Exception;
use think\exception\DbException;
use think\exception\PDOException;
use think\exception\ValidateException;
use think\response\Json;
/**
* 号码管理
*
* @icon fa fa-phone
* @remark 分流号码管理,关联分流链接
*/
class Number extends Backend
{
/** @var NumberModel */
protected $model = null;
protected $searchFields = 'ticket_name,number';
protected $dataLimit = 'personal';
protected $modelValidate = true;
protected $modelSceneValidate = true;
/** @var string */
protected $multiFields = 'status,manual_manage';
2026-06-04 14:15:12 +08:00
/** @var string[] */
protected $noNeedRight = ['script'];
/** @var string */
private const PATCH_VIEW_DIR = 'patches/application/admin/view/split/number/';
public function _initialize()
{
parent::_initialize();
$lang = $this->request->langset();
$lang = preg_match('/^([a-zA-Z\-_]{2,10})$/i', $lang) ? $lang : 'zh-cn';
$langFile = ROOT_PATH . 'patches/application/admin/lang/' . $lang . '/split/number.php';
if (is_file($langFile)) {
\think\Lang::load($langFile);
}
$this->model = new NumberModel();
$this->view->assign('numberTypeList', $this->model->getNumberTypeList());
$this->view->assign('statusList', $this->model->getStatusList());
$this->view->assign('manualManageList', $this->model->getManualManageList());
$this->view->assign('splitLinkList', $this->buildSplitLinkList());
$this->assignconfig('numberTypeList', $this->model->getNumberTypeList());
$this->assignconfig('statusList', $this->model->getStatusList());
$this->assignconfig('manualManageList', $this->model->getManualManageList());
2026-06-09 03:36:30 +08:00
$this->assignconfig('platformStatusList', $this->model->getPlatformStatusList());
2026-06-04 14:15:12 +08:00
$this->setupPatchFrontend();
}
private function setupPatchFrontend(): void
{
$patchJs = ROOT_PATH . 'patches/public/assets/js/backend/split/number.js';
$publicJs = ROOT_PATH . 'public/assets/js/backend/split/number.js';
$usePatchJs = is_file($patchJs) && (
!is_file($publicJs) || filemtime($patchJs) >= filemtime($publicJs)
);
if (!$usePatchJs) {
return;
}
$cfg = is_array($this->view->config ?? null) ? $this->view->config : [];
$version = (string) \think\Config::get('site.version');
// 使用站内相对路径,避免生产环境 domain() 与反向代理/HTTPS 不一致导致 JS 跨域丢 Cookie
$scriptUrl = (string) url('split.number/script', ['v' => $version], '', false);
2026-06-04 14:15:12 +08:00
$cfg['jsname'] = $scriptUrl;
$this->view->assign('config', $cfg);
$this->view->config = $cfg;
}
/**
* @return array<int, array<string, string>>
*/
private function buildSplitLinkList(): array
{
$query = (new LinkModel())->where('status', 'normal')->order('id', 'desc');
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('admin_id', 'in', $adminIds);
}
}
$list = [];
foreach ($query->select() as $row) {
$code = (string) $row['link_code'];
$desc = (string) $row['description'];
$label = $desc !== '' ? $code . ' - ' . $desc : $code;
$list[] = [
'id' => (string) $row['id'],
'label' => $label,
];
}
return $list;
}
private function fetchPatch(string $template): string
{
$patchFile = ROOT_PATH . self::PATCH_VIEW_DIR . $template . '.html';
$appFile = APP_PATH . 'admin/view/split/number/' . $template . '.html';
if (is_file($patchFile)) {
$file = $patchFile;
} elseif (is_file($appFile)) {
$file = $appFile;
} else {
$this->error('模板文件不存在');
}
return (string) $this->view->fetch($file);
}
/**
* 列表状态开关:手动关闭时标记 manual_manage=1,防止同步自动恢复开启
*
* @param string $ids
*/
public function multi($ids = '')
{
if (!$this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$ids = $ids ?: $this->request->post('ids', '');
if ($ids === '') {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
$params = $this->request->post('params', '');
parse_str((string) $params, $values);
if (!isset($values['status'])) {
parent::multi($ids);
return;
}
if ((string) $values['status'] === 'hidden') {
$values['manual_manage'] = 1;
} elseif ((string) $values['status'] === 'normal') {
$values['manual_manage'] = 0;
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
$count = 0;
Db::startTrans();
try {
$list = $this->model->where($this->model->getPk(), 'in', $ids)->select();
foreach ($list as $item) {
$count += $item->allowField(true)->isUpdate(true)->save($values);
}
Db::commit();
} catch (ValidateException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count > 0) {
$this->success();
}
$this->error(__('No rows were updated'));
}
2026-06-04 14:15:12 +08:00
/**
* @return string|Json
* @throws DbException
*/
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
return $this->fetchPatch('index');
}
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
$list = $this->model
->with(['splitLink'])
->where($where)
->order($sort, $order)
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result);
}
/**
* @return string
* @throws DbException
*/
public function add()
{
if (false === $this->request->isPost()) {
return $this->fetchPatch('add');
}
$params = $this->request->post('row/a', []);
if ($params === []) {
$this->error(__('Parameter %s can not be empty', ''));
}
$params = $this->preExcludeFields($params);
if (($params['number_type'] ?? '') !== 'custom') {
$params['number_type_custom'] = '';
}
$numbersText = (string) ($params['numbers'] ?? '');
unset($params['numbers']);
$numberList = NumberModel::parseNumbersText($numbersText);
if ($numberList === []) {
$this->error(__('Please fill at least one number'));
}
$validateData = array_merge($params, ['numbers' => $numbersText]);
$adminId = $this->dataLimit && $this->dataLimitFieldAutoFill ? (int) $this->auth->id : 0;
$inserted = 0;
$skipped = 0;
Db::startTrans();
try {
if ($this->modelValidate) {
$name = str_replace('\\model\\', '\\validate\\', get_class($this->model));
$validate = $this->modelSceneValidate ? $name . '.add' : $name;
$this->model->validateFailException()->validate($validate, $validateData);
}
$splitLinkId = (int) ($params['split_link_id'] ?? 0);
$baseRow = [
'split_link_id' => $splitLinkId,
'ticket_name' => (string) ($params['ticket_name'] ?? ''),
'number_type' => (string) ($params['number_type'] ?? ''),
'number_type_custom' => (string) ($params['number_type_custom'] ?? ''),
'status' => (string) ($params['status'] ?? 'normal'),
'visit_count' => 0,
'inbound_count' => 0,
'manual_manage' => 0,
];
if ($adminId > 0) {
$baseRow['admin_id'] = $adminId;
}
foreach ($numberList as $num) {
$exists = $this->model
->where('split_link_id', $splitLinkId)
->where('number', $num)
->find();
if ($exists) {
$skipped++;
continue;
}
$row = $baseRow;
$row['number'] = $num;
$item = new NumberModel();
$item->allowField(true)->isUpdate(false)->save($row);
$inserted++;
}
if ($inserted === 0) {
throw new Exception(__('All numbers already exist for this link'));
}
Db::commit();
} catch (ValidateException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
$msg = __('Inserted %d number(s)', $inserted);
if ($skipped > 0) {
$msg .= '' . __('Skipped %d duplicate(s)', $skipped);
}
$this->success($msg);
}
/**
* @param string|null $ids
* @return string
* @throws DbException
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds) && !in_array((int) $row[$this->dataLimitField], $adminIds, true)) {
$this->error(__('You have no permission'));
}
if (false === $this->request->isPost()) {
$this->view->assign('row', $row);
return $this->fetchPatch('edit');
}
$params = $this->request->post('row/a', []);
if ($params === []) {
$this->error(__('Parameter %s can not be empty', ''));
}
$params = $this->preExcludeFields($params);
if (($params['number_type'] ?? '') !== 'custom') {
$params['number_type_custom'] = '';
}
$newNumber = trim((string) ($params['number'] ?? ''));
$splitLinkId = (int) ($params['split_link_id'] ?? $row['split_link_id']);
$exists = $this->model
->where('split_link_id', $splitLinkId)
->where('number', $newNumber)
->where('id', '<>', (int) $row['id'])
->find();
if ($exists) {
$this->error(__('Number already exists for this link'));
}
$result = false;
Db::startTrans();
try {
if ($this->modelValidate) {
$name = str_replace('\\model\\', '\\validate\\', get_class($this->model));
$validate = $this->modelSceneValidate ? $name . '.edit' : $name;
$row->validateFailException()->validate($validate, $params);
}
$result = $row->allowField(true)->save($params);
Db::commit();
} catch (ValidateException $e) {
Db::rollback();
$this->error($e->getMessage());
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result === false) {
$this->error(__('No rows were updated'));
}
$this->success();
}
/**
* 批量更新号码状态与手动管理
*/
public function batchupdate(): void
{
if (false === $this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$ids = $this->request->post('ids', '');
if ($ids === '' || $ids === null) {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
if (!is_array($ids)) {
$ids = explode(',', (string) $ids);
}
$ids = array_filter(array_map('intval', $ids));
if ($ids === []) {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
$status = (string) $this->request->post('status', '');
$manualManage = $this->request->post('manual_manage', '');
$statusList = $this->model->getStatusList();
if (!isset($statusList[$status])) {
$this->error(__('Invalid status'));
}
if (!in_array((string) $manualManage, ['0', '1'], true)) {
$this->error(__('Invalid manual manage'));
}
$query = $this->model->where('id', 'in', $ids);
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('admin_id', 'in', $adminIds);
}
}
$count = $query->update([
'status' => $status,
'manual_manage' => (int) $manualManage,
]);
if ($count === false || $count === 0) {
$this->error(__('No rows were updated'));
}
$this->success(__('Batch update success'));
}
/**
* 排除不可由表单提交的字段
*
* @param array<string, mixed> $params
* @return array<string, mixed>
*/
protected function preExcludeFields($params): array
{
$params = parent::preExcludeFields($params);
unset(
$params['visit_count'],
$params['inbound_count'],
$params['manual_manage'],
$params['id']
);
return $params;
}
public function script(): void
{
$jsFile = ROOT_PATH . 'patches/public/assets/js/backend/split/number.js';
if (!is_file($jsFile)) {
$jsFile = ROOT_PATH . 'public/assets/js/backend/split/number.js';
}
if (!is_file($jsFile)) {
$this->error('脚本文件不存在');
}
$content = file_get_contents($jsFile);
if ($content === false) {
$this->error('读取脚本失败');
}
$response = response($content, 200, [
'Content-Type' => 'application/javascript; charset=utf-8',
'Cache-Control' => 'public, max-age=3600',
]);
throw new \think\exception\HttpResponseException($response);
}
}