添加whatshub工单

This commit is contained in:
root
2026-06-20 04:47:34 +08:00
parent a6c87e8e25
commit d5a0ffa6db
271 changed files with 9377 additions and 303 deletions
+25
View File
@@ -64,10 +64,35 @@ class Link extends Backend
$this->assignconfig('ipProtectList', $this->model->getIpProtectList());
$this->assignconfig('randomShuffleList', $this->model->getRandomShuffleList());
$this->assignconfig('statusList', $this->model->getStatusList());
$this->assignconfig('linkFilterList', $this->buildLinkFilterList());
$this->setupPatchFrontend();
}
/**
* 分流链接列表筛选下拉(id => 链接码 - 描述)
*
* @return array<string, string>
*/
private function buildLinkFilterList(): array
{
$query = $this->model->order('id', 'desc');
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('admin_id', 'in', $adminIds);
}
}
$list = [];
foreach ($query->field('id,link_code,description')->select() as $row) {
$code = (string) $row['link_code'];
$desc = (string) $row['description'];
$label = $desc !== '' ? $code . ' - ' . $desc : $code;
$list[(string) $row['id']] = $label;
}
return $list;
}
/**
* 未部署 JS 到 public 时,或 patches 版本较新时,jsname 指向 script 接口
*/
+130
View File
@@ -29,6 +29,9 @@ class Number extends Backend
protected $dataLimit = 'personal';
/** 关联 splitLink 预载入时需为筛选/排序字段加表别名,避免 status 等列歧义 */
protected $relationSearch = true;
protected $modelValidate = true;
protected $modelSceneValidate = true;
@@ -62,6 +65,8 @@ class Number extends Backend
$this->assignconfig('statusList', $this->model->getStatusList());
$this->assignconfig('manualManageList', $this->model->getManualManageList());
$this->assignconfig('platformStatusList', $this->model->getPlatformStatusList());
$this->assignconfig('splitLinkFilterList', $this->buildNumberSplitLinkFilterList());
$this->assignconfig('splitLinkSelectList', $this->buildSplitLinkSelectConfig());
$this->setupPatchFrontend();
}
@@ -111,6 +116,52 @@ class Number extends Backend
return $list;
}
/**
* 已有号码关联的分流链接(供列表筛选下拉)
*
* @return array<string, string>
*/
private function buildNumberSplitLinkFilterList(): array
{
$numberTable = $this->model->getTable();
$linkTable = (new LinkModel())->getTable();
$query = Db::table($numberTable)
->alias('n')
->join($linkTable . ' l', 'n.split_link_id = l.id')
->where('n.split_link_id', '>', 0)
->group('n.split_link_id')
->field('l.id,l.link_code,l.description')
->order('l.id', 'desc');
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('n.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[(string) $row['id']] = $label;
}
return $list;
}
/**
* 全部分流链接(供批量操作弹窗可搜索下拉)
*
* @return array<string, string>
*/
private function buildSplitLinkSelectConfig(): array
{
$list = [];
foreach ($this->buildSplitLinkList() as $row) {
$list[(string) $row['id']] = (string) $row['label'];
}
return $list;
}
private function fetchPatch(string $template): string
{
$patchFile = ROOT_PATH . self::PATCH_VIEW_DIR . $template . '.html';
@@ -395,6 +446,85 @@ class Number extends Backend
$this->success(__('Batch update success'));
}
/**
* 按分流链接 + 号码文本批量开启/关闭/删除
*/
public function batchoperate(): void
{
if (false === $this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$splitLinkId = (int) $this->request->post('split_link_id', 0);
$numbersText = (string) $this->request->post('numbers', '');
$action = (string) $this->request->post('action', '');
if ($splitLinkId <= 0) {
$this->error(__('Please select split link'));
}
$numberList = NumberModel::parseNumbersText($numbersText);
if ($numberList === []) {
$this->error(__('Please fill at least one number'));
}
$allowedActions = ['enable', 'disable', 'delete'];
if (!in_array($action, $allowedActions, true)) {
$this->error(__('Invalid batch operate action'));
}
$linkQuery = (new LinkModel())->where('id', $splitLinkId)->where('status', 'normal');
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$linkQuery->where('admin_id', 'in', $adminIds);
}
}
if (!$linkQuery->find()) {
$this->error(__('No Results were found'));
}
$query = $this->model
->where('split_link_id', $splitLinkId)
->where('number', 'in', $numberList);
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('admin_id', 'in', $adminIds);
}
}
$count = 0;
Db::startTrans();
try {
if ($action === 'delete') {
$rows = $query->select();
foreach ($rows as $row) {
if ($row->delete()) {
$count++;
}
}
} else {
$status = $action === 'enable' ? 'normal' : 'hidden';
$manualManage = $action === 'enable' ? 0 : 1;
$count = (int) $query->update([
'status' => $status,
'manual_manage' => $manualManage,
]);
}
Db::commit();
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count <= 0) {
$this->error(__('No matching numbers found'));
}
$this->success(sprintf(__('Batch operate success'), $count));
}
/**
* 排除不可由表单提交的字段
*
+129 -1
View File
@@ -58,6 +58,7 @@ class Ticket extends Backend
$this->assignconfig('ticketTypeList', $this->model->getTicketTypeList());
$this->assignconfig('numberTypeList', $this->model->getNumberTypeList());
$this->assignconfig('statusList', $this->model->getStatusList());
$this->assignconfig('splitLinkFilterList', $this->buildTicketSplitLinkFilterList());
$this->assignconfig([
'syncConfirmMsg' => __('Sync confirm'),
'syncBackgroundStartedMsg' => __('Sync background started'),
@@ -142,6 +143,71 @@ class Ticket extends Backend
return $list;
}
/**
* 已有工单关联的分流链接(供列表筛选下拉)
*
* @return array<string, string> id => label
*/
private function buildTicketSplitLinkFilterList(): array
{
$ticketTable = $this->model->getTable();
$linkTable = (new LinkModel())->getTable();
$query = Db::table($ticketTable)
->alias('t')
->join($linkTable . ' l', 't.split_link_id = l.id')
->where('t.split_link_id', '>', 0)
->group('t.split_link_id')
->field('l.id,l.link_code,l.description')
->order('l.id', 'desc');
if ($this->dataLimit) {
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$query->where('t.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[(string) $row['id']] = $label;
}
return $list;
}
/**
* 按当前筛选条件汇总列表统计列
*
* @param callable|array<mixed> $where buildparams() 返回的闭包或条件数组
* @return array<string, int|float|string>
*/
private function buildListSummary($where): array
{
$stats = $this->model
->where($where)
->fieldRaw(
'COALESCE(SUM(ticket_total), 0) AS sum_ticket_total,'
. ' COALESCE(SUM(complete_count), 0) AS sum_complete_count,'
. ' COALESCE(SUM(speed_per_hour), 0) AS sum_speed_per_hour,'
. ' COALESCE(AVG(CASE WHEN ticket_total > 0 THEN complete_count * 100.0 / ticket_total ELSE 0 END), 0) AS avg_ticket_progress'
)
->find();
if (!$stats) {
return [
'ticket_total' => 0,
'complete_count' => 0,
'speed_per_hour' => '0.00',
'ticket_progress_text' => '0.00%',
];
}
return [
'ticket_total' => (int) ($stats['sum_ticket_total'] ?? 0),
'complete_count' => (int) ($stats['sum_complete_count'] ?? 0),
'speed_per_hour' => number_format((float) ($stats['sum_speed_per_hour'] ?? 0), 2, '.', ''),
'ticket_progress_text' => number_format((float) ($stats['avg_ticket_progress'] ?? 0), 2, '.', '') . '%',
];
}
private function fetchPatch(string $template): string
{
$patchFile = ROOT_PATH . self::PATCH_VIEW_DIR . $template . '.html';
@@ -179,7 +245,11 @@ class Ticket extends Backend
->where($where)
->order($sort, $order)
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
$result = [
'total' => $list->total(),
'rows' => $list->items(),
'summary' => $this->buildListSummary($where),
];
return json($result);
}
@@ -331,6 +401,10 @@ class Ticket extends Backend
public function add()
{
if (false === $this->request->isPost()) {
$copyRow = $this->buildCopyRowData();
if ($copyRow !== null) {
$this->view->assign('row', $copyRow);
}
return $this->fetchPatch('add');
}
$params = $this->request->post('row/a', []);
@@ -367,6 +441,60 @@ class Ticket extends Backend
$this->success('', null, ['id' => (int) $this->model->id]);
}
/**
* 从 copy_id 构建添加工单表单的预填数据(仅复制业务字段,不含同步统计)
*
* @return array<string, mixed>|null
*/
private function buildCopyRowData(): ?array
{
$copyId = (int) $this->request->get('copy_id/d', 0);
if ($copyId <= 0) {
return null;
}
$row = $this->model->get($copyId);
if (!$row) {
return null;
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds) && !in_array((int) $row[$this->dataLimitField], $adminIds, true)) {
return null;
}
$data = $row->toArray();
$copyFields = [
'ticket_type',
'ticket_name',
'ticket_url',
'ticket_total',
'split_link_id',
'number_type',
'number_type_custom',
'order_limit',
'assign_ratio',
'account',
'password',
];
$copyRow = [];
foreach ($copyFields as $field) {
if (array_key_exists($field, $data)) {
$copyRow[$field] = $data[$field];
}
}
$copyRow['start_time'] = $data['start_time_text'] ?? '';
$copyRow['end_time'] = $data['end_time_text'] ?? '';
$name = trim((string) ($copyRow['ticket_name'] ?? ''));
if ($name !== '' && mb_strpos($name, ' (副本)') === false) {
$copyRow['ticket_name'] = $name . ' (副本)';
}
return $copyRow;
}
/**
* @param string|null $ids
* @return string