号码管理
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model\split;
|
||||
|
||||
use app\common\service\SplitPlatformDomainService;
|
||||
use think\Db;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 分流号码模型
|
||||
*/
|
||||
class Number extends Model
|
||||
{
|
||||
protected $name = 'split_number';
|
||||
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
protected $deleteTime = false;
|
||||
|
||||
protected $append = [
|
||||
'number_type_text',
|
||||
'link_url_text',
|
||||
'status_text',
|
||||
'manual_manage_text',
|
||||
];
|
||||
|
||||
/**
|
||||
* 号码类型(与工单模块一致)
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getNumberTypeList(): array
|
||||
{
|
||||
return [
|
||||
'whatsapp' => 'WhatsApp',
|
||||
'telegram' => 'Telegram',
|
||||
'line' => 'Line',
|
||||
'custom' => __('Number type custom'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getStatusList(): array
|
||||
{
|
||||
return [
|
||||
'normal' => __('Status normal'),
|
||||
'hidden' => __('Status hidden'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getManualManageList(): array
|
||||
{
|
||||
return [
|
||||
'0' => __('Manual manage no'),
|
||||
'1' => __('Manual manage yes'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联分流链接
|
||||
*/
|
||||
public function splitLink()
|
||||
{
|
||||
return $this->belongsTo(Link::class, 'split_link_id', 'id', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
||||
|
||||
public function setNumberTypeCustomAttr($value): string
|
||||
{
|
||||
return trim((string) $value);
|
||||
}
|
||||
|
||||
public function setManualManageAttr($value): int
|
||||
{
|
||||
return (int) $value === 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
public function getNumberTypeTextAttr($value, $data): string
|
||||
{
|
||||
$type = (string) ($data['number_type'] ?? '');
|
||||
if ($type === 'custom') {
|
||||
$custom = trim((string) ($data['number_type_custom'] ?? ''));
|
||||
return $custom !== '' ? $custom : (string) __('Number type custom');
|
||||
}
|
||||
$list = $this->getNumberTypeList();
|
||||
return $list[$type] ?? $type;
|
||||
}
|
||||
|
||||
public function getLinkUrlTextAttr($value, $data): string
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
return (string) $value;
|
||||
}
|
||||
$linkId = (int) ($data['split_link_id'] ?? 0);
|
||||
if ($linkId <= 0) {
|
||||
return '';
|
||||
}
|
||||
$code = Link::where('id', $linkId)->value('link_code');
|
||||
return self::buildLinkUrl((string) $code);
|
||||
}
|
||||
|
||||
public function getStatusTextAttr($value, $data): string
|
||||
{
|
||||
$key = (string) ($data['status'] ?? '');
|
||||
$list = $this->getStatusList();
|
||||
return $list[$key] ?? $key;
|
||||
}
|
||||
|
||||
public function getManualManageTextAttr($value, $data): string
|
||||
{
|
||||
$key = (string) ((int) ($data['manual_manage'] ?? 0));
|
||||
$list = $this->getManualManageList();
|
||||
return $list[$key] ?? $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据链接码生成完整分流 URL
|
||||
*/
|
||||
public static function buildLinkUrl(string $linkCode): string
|
||||
{
|
||||
$linkCode = trim($linkCode);
|
||||
if ($linkCode === '') {
|
||||
return '';
|
||||
}
|
||||
$raw = trim((string) \think\Config::get('site.split_platform_domain'));
|
||||
if ($raw === '') {
|
||||
$raw = trim((string) Db::name('config')->where('name', 'split_platform_domain')->value('value'));
|
||||
}
|
||||
$domains = SplitPlatformDomainService::parseList($raw);
|
||||
$domain = $domains[0] ?? '';
|
||||
if ($domain === '') {
|
||||
return '';
|
||||
}
|
||||
return 'https://' . $domain . '/s/' . $linkCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 textarea 号码列表
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function parseNumbersText(string $text): array
|
||||
{
|
||||
$lines = preg_split('/\r\n|\r|\n/', $text) ?: [];
|
||||
$numbers = [];
|
||||
foreach ($lines as $line) {
|
||||
$num = trim((string) $line);
|
||||
if ($num === '') {
|
||||
continue;
|
||||
}
|
||||
$numbers[$num] = $num;
|
||||
}
|
||||
return array_values($numbers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model\split;
|
||||
|
||||
use think\Db;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 分流工单模型
|
||||
*/
|
||||
class Ticket extends Model
|
||||
{
|
||||
protected $name = 'split_ticket';
|
||||
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
protected $deleteTime = false;
|
||||
|
||||
protected $append = [
|
||||
'ticket_type_text',
|
||||
'number_type_text',
|
||||
'link_code_text',
|
||||
'start_time_text',
|
||||
'end_time_text',
|
||||
'status_text',
|
||||
'click_count',
|
||||
'ticket_progress_text',
|
||||
'inbound_ratio_text',
|
||||
'sync_display_text',
|
||||
];
|
||||
|
||||
/**
|
||||
* 工单类型
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getTicketTypeList(): array
|
||||
{
|
||||
return [
|
||||
'xinghe' => __('Ticket type xinghe'),
|
||||
'haiwang' => __('Ticket type haiwang'),
|
||||
'taiji' => __('Ticket type taiji'),
|
||||
'huojian' => __('Ticket type huojian'),
|
||||
'ss_channel' => __('Ticket type ss_channel'),
|
||||
'ss_customer' => __('Ticket type ss_customer'),
|
||||
'yifafa' => __('Ticket type yifafa'),
|
||||
'a2c' => __('Ticket type a2c'),
|
||||
'ceo_scrm' => __('Ticket type ceo_scrm'),
|
||||
'whatshub' => __('Ticket type whatshub'),
|
||||
'sihai' => __('Ticket type sihai'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getStatusList(): array
|
||||
{
|
||||
return [
|
||||
'normal' => __('Status normal'),
|
||||
'hidden' => __('Status hidden'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getSyncStatusList(): array
|
||||
{
|
||||
return [
|
||||
'success' => __('Sync status success'),
|
||||
'error' => __('Sync status error'),
|
||||
'pending' => __('Sync status pending'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 号码类型
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getNumberTypeList(): array
|
||||
{
|
||||
return [
|
||||
'whatsapp' => 'Whatsapp',
|
||||
'telegram' => 'Telegram',
|
||||
'line' => 'Line',
|
||||
'custom' => __('Number type custom'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联分流链接
|
||||
*/
|
||||
public function splitLink()
|
||||
{
|
||||
return $this->belongsTo(Link::class, 'split_link_id', 'id', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表子查询注入的点击数,无则按关联号码汇总
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function getClickCountAttr($value, $data): int
|
||||
{
|
||||
if (isset($data['click_count']) && $data['click_count'] !== '' && $data['click_count'] !== null) {
|
||||
return (int) $data['click_count'];
|
||||
}
|
||||
return self::sumVisitCountForTicket($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工单进度:完成数量 / 工单总量
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function getTicketProgressTextAttr($value, $data): string
|
||||
{
|
||||
$total = (int) ($data['ticket_total'] ?? 0);
|
||||
$complete = (int) ($data['complete_count'] ?? 0);
|
||||
if ($total <= 0) {
|
||||
return '—';
|
||||
}
|
||||
$percent = round($complete / $total * 100, 2);
|
||||
return $percent . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* 进线比例:进线人数 / 点击数(点击数=关联号码 visit_count 之和)
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function getInboundRatioTextAttr($value, $data): string
|
||||
{
|
||||
$clicks = $this->getClickCountAttr(null, $data);
|
||||
$inbound = (int) ($data['inbound_count'] ?? 0);
|
||||
if ($clicks <= 0) {
|
||||
return '—';
|
||||
}
|
||||
$percent = round($inbound / $clicks * 100, 2);
|
||||
return $percent . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步状态展示文案
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function getSyncDisplayTextAttr($value, $data): string
|
||||
{
|
||||
$status = (string) ($data['sync_status'] ?? 'pending');
|
||||
$online = (int) ($data['online_count'] ?? 0);
|
||||
if ($status === 'success') {
|
||||
return sprintf((string) __('Sync display success'), $online);
|
||||
}
|
||||
return (string) __('Sync display error');
|
||||
}
|
||||
|
||||
public function setStartTimeAttr($value): ?int
|
||||
{
|
||||
return self::parseTimeValue($value);
|
||||
}
|
||||
|
||||
public function setEndTimeAttr($value): ?int
|
||||
{
|
||||
return self::parseTimeValue($value);
|
||||
}
|
||||
|
||||
public function setNumberTypeCustomAttr($value): string
|
||||
{
|
||||
return trim((string) $value);
|
||||
}
|
||||
|
||||
public function setTicketTotalAttr($value): int
|
||||
{
|
||||
return max(0, (int) $value);
|
||||
}
|
||||
|
||||
public function getTicketTypeTextAttr($value, $data): string
|
||||
{
|
||||
$key = (string) ($data['ticket_type'] ?? '');
|
||||
$list = $this->getTicketTypeList();
|
||||
return $list[$key] ?? $key;
|
||||
}
|
||||
|
||||
public function getNumberTypeTextAttr($value, $data): string
|
||||
{
|
||||
$type = (string) ($data['number_type'] ?? '');
|
||||
if ($type === 'custom') {
|
||||
$custom = trim((string) ($data['number_type_custom'] ?? ''));
|
||||
return $custom !== '' ? $custom : (string) __('Number type custom');
|
||||
}
|
||||
$list = $this->getNumberTypeList();
|
||||
return $list[$type] ?? $type;
|
||||
}
|
||||
|
||||
public function getLinkCodeTextAttr($value, $data): string
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
return (string) $value;
|
||||
}
|
||||
$linkId = (int) ($data['split_link_id'] ?? 0);
|
||||
if ($linkId <= 0) {
|
||||
return '';
|
||||
}
|
||||
$code = Link::where('id', $linkId)->value('link_code');
|
||||
return (string) $code;
|
||||
}
|
||||
|
||||
public function getStartTimeTextAttr($value, $data): string
|
||||
{
|
||||
return self::formatTimeText($data['start_time'] ?? null);
|
||||
}
|
||||
|
||||
public function getEndTimeTextAttr($value, $data): string
|
||||
{
|
||||
return self::formatTimeText($data['end_time'] ?? null);
|
||||
}
|
||||
|
||||
public function getStatusTextAttr($value, $data): string
|
||||
{
|
||||
$key = (string) ($data['status'] ?? '');
|
||||
$list = $this->getStatusList();
|
||||
return $list[$key] ?? $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建列表用 click_count 子查询 SQL 片段
|
||||
*/
|
||||
public static function buildClickCountSubQuery(string $ticketTableAlias = ''): string
|
||||
{
|
||||
$ticketTable = (new self())->getTable();
|
||||
$numberTable = (new Number())->getTable();
|
||||
$t = $ticketTableAlias !== '' ? $ticketTableAlias : $ticketTable;
|
||||
return "(SELECT COALESCE(SUM(n.visit_count), 0) FROM `{$numberTable}` n "
|
||||
. "WHERE n.ticket_name = `{$t}`.ticket_name "
|
||||
. "AND n.split_link_id = `{$t}`.split_link_id "
|
||||
. "AND n.admin_id = `{$t}`.admin_id) AS click_count";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private static function sumVisitCountForTicket(array $data): int
|
||||
{
|
||||
$ticketName = (string) ($data['ticket_name'] ?? '');
|
||||
$linkId = (int) ($data['split_link_id'] ?? 0);
|
||||
$adminId = (int) ($data['admin_id'] ?? 0);
|
||||
if ($ticketName === '' || $linkId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
$sum = Db::name('split_number')
|
||||
->where('ticket_name', $ticketName)
|
||||
->where('split_link_id', $linkId)
|
||||
->where('admin_id', $adminId)
|
||||
->sum('visit_count');
|
||||
return (int) $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function parseTimeValue($value): ?int
|
||||
{
|
||||
if ($value === '' || $value === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
$ts = strtotime((string) $value);
|
||||
return $ts === false ? null : $ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function formatTimeText($value): string
|
||||
{
|
||||
if ($value === '' || $value === null || !is_numeric($value)) {
|
||||
return '';
|
||||
}
|
||||
$ts = (int) $value;
|
||||
return $ts > 0 ? date('Y-m-d H:i:s', $ts) : '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user