Files
links/patches/application/admin/controller/split/Ticket.php
T
2026-06-04 14:15:12 +08:00

307 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\admin\controller\split;
use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Ticket as TicketModel;
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-ticket
* @remark 分流工单管理,关联分流链接
*/
class Ticket extends Backend
{
/** @var \app\admin\model\split\Ticket */
protected $model = null;
protected $searchFields = 'ticket_name,ticket_url,ticket_total';
protected $dataLimit = 'personal';
protected $modelValidate = true;
protected $modelSceneValidate = true;
/** @var string[] 无需鉴权的方法 */
protected $noNeedRight = ['script'];
/** @var string patches 视图目录 */
private const PATCH_VIEW_DIR = 'patches/application/admin/view/split/ticket/';
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/ticket.php';
if (is_file($langFile)) {
\think\Lang::load($langFile);
}
$linkLangFile = ROOT_PATH . 'patches/application/admin/lang/' . $lang . '/split/link.php';
if (is_file($linkLangFile)) {
\think\Lang::load($linkLangFile);
}
$this->model = new \app\admin\model\split\Ticket();
$this->view->assign('ticketTypeList', $this->model->getTicketTypeList());
$this->view->assign('numberTypeList', $this->model->getNumberTypeList());
$this->view->assign('statusList', $this->model->getStatusList());
$this->view->assign('splitLinkList', $this->buildSplitLinkList());
$this->assignconfig('ticketTypeList', $this->model->getTicketTypeList());
$this->assignconfig('numberTypeList', $this->model->getNumberTypeList());
$this->assignconfig('statusList', $this->model->getStatusList());
$this->setupPatchFrontend();
}
/**
* 未部署 JS 时指向 script 接口
*/
private function setupPatchFrontend(): void
{
$patchJs = ROOT_PATH . 'patches/public/assets/js/backend/split/ticket.js';
$publicJs = ROOT_PATH . 'public/assets/js/backend/split/ticket.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');
$scriptUrl = (string) url('split.ticket/script', ['v' => $version], false, true);
if (strpos($scriptUrl, '?') === false) {
$scriptUrl .= '?v=' . $version;
}
if (strpos($scriptUrl, '://') === false) {
$scriptUrl = $this->request->domain() . $scriptUrl;
}
$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/ticket/' . $template . '.html';
if (is_file($patchFile)) {
$file = $patchFile;
} elseif (is_file($appFile)) {
$file = $appFile;
} else {
$this->error('模板文件不存在');
}
return (string) $this->view->fetch($file);
}
/**
* @return string|Json
* @throws DbException
* @throws \think\Exception
*/
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();
$ticketTable = $this->model->getTable();
$clickSub = TicketModel::buildClickCountSubQuery();
// 勿 with(splitLink)eagerlyType=0 为 JOIN 预载入,会重写 field 导致子查询 SQL 语法错误
$list = $this->model
->field($ticketTable . '.*,' . $clickSub)
->where($where)
->order($sort, $order)
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result);
}
/**
* 同步统计字段仅由接口写入,禁止表单提交篡改
*
* @param array<string, mixed> $params
* @return array<string, mixed>
*/
protected function preExcludeFields($params): array
{
$params = parent::preExcludeFields($params);
unset(
$params['complete_count'],
$params['inbound_count'],
$params['speed_per_hour'],
$params['number_count'],
$params['number_offline_count'],
$params['number_banned_count'],
$params['online_count'],
$params['sync_status'],
$params['sync_time'],
$params['sync_message'],
$params['click_count']
);
return $params;
}
/**
* @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'] = '';
}
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
$params[$this->dataLimitField] = $this->auth->id;
}
$result = false;
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);
}
$result = $this->model->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 inserted'));
}
$this->success();
}
/**
* @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()) {
$rowData = $row->toArray();
$rowData['start_time'] = $rowData['start_time_text'] ?? '';
$rowData['end_time'] = $rowData['end_time_text'] ?? '';
$this->view->assign('row', $rowData);
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'] = '';
}
$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);
}
$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();
}
/**
* 输出后台 JSpatches 未部署到 public 时)
*/
public function script(): void
{
$jsFile = ROOT_PATH . 'patches/public/assets/js/backend/split/ticket.js';
if (!is_file($jsFile)) {
$jsFile = ROOT_PATH . 'public/assets/js/backend/split/ticket.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);
}
}