Merge branch 'develop' into yongpxu-dev2
This commit is contained in:
@@ -38,7 +38,7 @@ export default function ContentForm() {
|
||||
GroupSelectionItem[]
|
||||
>([]);
|
||||
const [useAI, setUseAI] = useState(false);
|
||||
const [aiPrompt, setAIPrompt] = useState("");
|
||||
const [aiPrompt, setAIPrompt] = useState("重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除");
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
const [dateRange, setDateRange] = useState<[Date | null, Date | null]>([
|
||||
null,
|
||||
@@ -296,12 +296,13 @@ export default function ContentForm() {
|
||||
{useAI && (
|
||||
<div className={style["form-section"]}>
|
||||
<label className={style["form-label"]}>AI提示词</label>
|
||||
<AntdInput
|
||||
placeholder="请输入AI提示词"
|
||||
value={aiPrompt}
|
||||
onChange={e => setAIPrompt(e.target.value)}
|
||||
className={style["input"]}
|
||||
/>
|
||||
<TextArea
|
||||
placeholder="请输入AI提示词"
|
||||
value={aiPrompt}
|
||||
onChange={e => setAIPrompt(e.target.value)}
|
||||
className={style["input"]}
|
||||
autoSize={{ minRows: 4, maxRows: 10 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -27,3 +27,12 @@ export function getWechatFriends(params: {
|
||||
export function getWechatFriendDetail(id: string) {
|
||||
return request("/v1/WechatFriend/detail", { id }, "GET");
|
||||
}
|
||||
|
||||
// 好友转移接口
|
||||
export function transferWechatFriends(params: {
|
||||
wechatId: string;
|
||||
devices: number[];
|
||||
inherit: boolean;
|
||||
}) {
|
||||
return request("/v1/wechats/transfer-friends", params, "POST");
|
||||
}
|
||||
|
||||
@@ -582,6 +582,36 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.transfer-form {
|
||||
margin-top: 20px;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.form-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-control-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.switch-label {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Toast,
|
||||
Avatar,
|
||||
Tag,
|
||||
Switch,
|
||||
} from "antd-mobile";
|
||||
import { Input, Pagination } from "antd";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
@@ -20,7 +21,9 @@ import {
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./detail.module.scss";
|
||||
import { getWechatAccountDetail, getWechatFriends } from "./api";
|
||||
import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
import { WechatAccountSummary, Friend } from "./data";
|
||||
|
||||
@@ -33,6 +36,9 @@ const WechatAccountDetail: React.FC = () => {
|
||||
const [accountInfo, setAccountInfo] = useState<any>(null);
|
||||
const [showRestrictions, setShowRestrictions] = useState(false);
|
||||
const [showTransferConfirm, setShowTransferConfirm] = useState(false);
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>([]);
|
||||
const [inheritInfo, setInheritInfo] = useState(true);
|
||||
const [transferLoading, setTransferLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [loadingInfo, setLoadingInfo] = useState(true);
|
||||
@@ -181,16 +187,54 @@ const WechatAccountDetail: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleTransferFriends = () => {
|
||||
setSelectedDevices([]);
|
||||
setInheritInfo(true);
|
||||
setShowTransferConfirm(true);
|
||||
};
|
||||
|
||||
const confirmTransferFriends = () => {
|
||||
Toast.show({
|
||||
content: "好友转移计划已创建,请在场景获客中查看详情",
|
||||
position: "top",
|
||||
});
|
||||
setShowTransferConfirm(false);
|
||||
navigate("/scenarios");
|
||||
const confirmTransferFriends = async () => {
|
||||
if (!id) {
|
||||
Toast.show({
|
||||
content: "微信账号ID不存在",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDevices.length === 0) {
|
||||
Toast.show({
|
||||
content: "请选择至少一个目标设备",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setTransferLoading(true);
|
||||
|
||||
// 调用好友转移API
|
||||
await transferWechatFriends({
|
||||
wechatId: id,
|
||||
devices: selectedDevices.map(device => device.id),
|
||||
inherit: inheritInfo
|
||||
});
|
||||
|
||||
Toast.show({
|
||||
content: "好友转移计划已创建,请在场景获客中查看详情",
|
||||
position: "top",
|
||||
});
|
||||
setShowTransferConfirm(false);
|
||||
setSelectedDevices([]);
|
||||
navigate("/scenarios");
|
||||
} catch (error) {
|
||||
console.error("好友转移失败:", error);
|
||||
Toast.show({
|
||||
content: "好友转移失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setTransferLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getRestrictionLevelColor = (level: number) => {
|
||||
@@ -545,15 +589,54 @@ const WechatAccountDetail: React.FC = () => {
|
||||
<p className={style["popup-description"]}>
|
||||
确定要将该微信号的好友转移到其他账号吗?此操作将创建一个好友转移计划。
|
||||
</p>
|
||||
|
||||
<div className={style["transfer-form"]}>
|
||||
{/* 设备选择 */}
|
||||
<div className={style["form-item"]}>
|
||||
<div className={style["form-label"]}>迁移的新设备</div>
|
||||
<div className={style["form-control"]}>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={setSelectedDevices}
|
||||
placeholder="请选择目标设备"
|
||||
showSelectedList={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 同步原有信息 */}
|
||||
<div className={style["form-item"]}>
|
||||
<div className={style["form-label"]}>同步原有信息</div>
|
||||
<div className={style["form-control-switch"]}>
|
||||
<Switch
|
||||
checked={inheritInfo}
|
||||
onChange={setInheritInfo}
|
||||
/>
|
||||
<span className={style["switch-label"]}>
|
||||
{inheritInfo ? "是" : "否"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["popup-actions"]}>
|
||||
<Button block color="primary" onClick={confirmTransferFriends}>
|
||||
确认转移
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={confirmTransferFriends}
|
||||
loading={transferLoading}
|
||||
disabled={transferLoading}
|
||||
>
|
||||
{transferLoading ? "转移中..." : "确认转移"}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
color="danger"
|
||||
fill="outline"
|
||||
onClick={() => setShowTransferConfirm(false)}
|
||||
onClick={() => {
|
||||
setShowTransferConfirm(false);
|
||||
setSelectedDevices([]);
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
|
||||
@@ -162,6 +162,7 @@ class AccountController extends BaseController
|
||||
'createTime' => time(),
|
||||
'privilegeIds' => json_encode([])
|
||||
]);
|
||||
$this->setPrivileges(['id' => $result]);
|
||||
return successJson($res);
|
||||
} else {
|
||||
return errorJson($result);
|
||||
@@ -245,12 +246,14 @@ class AccountController extends BaseController
|
||||
'createTime' => time(),
|
||||
'lastUpdateTime' => 0
|
||||
]);
|
||||
|
||||
$this->setPrivileges(['id' => $departmentResult]);
|
||||
|
||||
} else {
|
||||
DB::rollback();
|
||||
return errorJson('创建部门失败:' . $departmentResult);
|
||||
}
|
||||
|
||||
|
||||
// 2. 创建账号
|
||||
$accountParams = [
|
||||
'userName' => $accountName,
|
||||
@@ -559,6 +562,7 @@ class AccountController extends BaseController
|
||||
'syncPrivilege' => true
|
||||
];
|
||||
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
@@ -690,6 +690,11 @@ class DeviceController extends BaseController
|
||||
'lastUpdateTime' => isset($item['lastUpdateTime']) ? ($item['lastUpdateTime'] == '0001-01-01T00:00:00' ? 0 : strtotime($item['lastUpdateTime'])) : 0
|
||||
];
|
||||
|
||||
if (!empty($data['alive'])){
|
||||
$data['aliveTime'] = time();
|
||||
}
|
||||
|
||||
|
||||
// 使用imei作为唯一性判断
|
||||
$device = DeviceModel::where('id', $item['id'])->find();
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ class WechatController extends BaseController
|
||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : $this->request->param('pageIndex', 0),
|
||||
'pageSize' => !empty($pageSize) ? $pageSize : $this->request->param('pageSize', 10)
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
@@ -51,7 +50,6 @@ class WechatController extends BaseController
|
||||
// 发送请求获取基本信息
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatAccount/list', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存基本数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
@@ -169,6 +167,12 @@ class WechatController extends BaseController
|
||||
'wechatAlive' => isset($data['wechatAlive'][$wechatId]) ? (int)$data['wechatAlive'][$wechatId] : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
if (!empty($updateData['wechatAlive'])) {
|
||||
$updateData['wechatAliveTime'] = time();
|
||||
}
|
||||
|
||||
|
||||
// 更新数据库
|
||||
Db::table('s2_wechat_account')
|
||||
->where('id', $wechatId)
|
||||
|
||||
@@ -13,6 +13,7 @@ Route::group('v1/', function () {
|
||||
Route::group('wechatFriend/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表
|
||||
Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情
|
||||
Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料
|
||||
});
|
||||
//群相关
|
||||
Route::group('wechatChatroom/', function () {
|
||||
@@ -26,6 +27,11 @@ Route::group('v1/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\CustomerServiceController@getList'); // 获取好友列表
|
||||
});
|
||||
|
||||
//账号管理
|
||||
Route::group('accounts/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AccountsController@getList'); // 获取账号列表
|
||||
});
|
||||
|
||||
//客服相关
|
||||
Route::group('message/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\MessageController@getList'); // 获取好友列表
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class AccountsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取账号列表(过滤掉后缀为 _offline 与 _delete 的账号)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 401);
|
||||
}
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录', 401);
|
||||
}
|
||||
|
||||
$page = max(1, intval($this->request->param('page', 1)));
|
||||
$limit = max(1, intval($this->request->param('limit', 10)));
|
||||
$keyword = trim((string)$this->request->param('keyword', ''));
|
||||
|
||||
$query = Db::table('s2_company_account')
|
||||
->alias('a')
|
||||
->where([
|
||||
['a.departmentId', '=', $companyId],
|
||||
['a.status', '=', 0],
|
||||
])
|
||||
->whereNotLike('a.userName', '%_offline')
|
||||
->whereNotLike('a.userName', '%_delete');
|
||||
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($subQuery) use ($keyword) {
|
||||
$likeKeyword = '%' . $keyword . '%';
|
||||
$subQuery->whereLike('a.userName', $likeKeyword)
|
||||
->whereOrLike('a.realName', $likeKeyword)
|
||||
->whereOrLike('a.nickname', $likeKeyword);
|
||||
});
|
||||
}
|
||||
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->field([
|
||||
'a.id',
|
||||
'a.userName',
|
||||
'a.realName',
|
||||
'a.nickname',
|
||||
'a.departmentId',
|
||||
'a.departmentName',
|
||||
'a.avatar'
|
||||
])
|
||||
->order('a.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,12 @@ class CustomerServiceController extends BaseController
|
||||
// 确保即使有空数组也不会报错,并且去除重复值
|
||||
$accountIds = array_unique(array_merge($accountIds1 ?: [], $accountIds2 ?: []));
|
||||
|
||||
|
||||
|
||||
$wechatAliveTime = time() - 86400 * 30;
|
||||
$list = Db::table('s2_wechat_account')
|
||||
->whereIn('id',$accountIds)
|
||||
->where('wechatAliveTime','>',$wechatAliveTime)
|
||||
->order('id desc')
|
||||
->group('id')
|
||||
->select();
|
||||
|
||||
@@ -20,7 +20,7 @@ class MessageController extends BaseController
|
||||
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId');
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region');
|
||||
|
||||
|
||||
// 构建好友子查询
|
||||
@@ -119,7 +119,11 @@ class MessageController extends BaseController
|
||||
$v['groupId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['groupId'] : '';
|
||||
$v['wechatAccountId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatAccountId'] : '';
|
||||
$v['wechatId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatId'] : '';
|
||||
$v['extendFields'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['extendFields'] : [];
|
||||
$v['region'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['region'] : '';
|
||||
$v['phone'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['phone'] : '';
|
||||
$v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : [];
|
||||
|
||||
$unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
|
||||
$v['aiType'] = isset($aiTypeData[$v['wechatFriendId']]) ? $aiTypeData[$v['wechatFriendId']] : 0;
|
||||
unset($v['chatroomId']);
|
||||
@@ -150,7 +154,6 @@ class MessageController extends BaseController
|
||||
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success($list);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,53 +23,9 @@ class WechatFriendController extends BaseController
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
|
||||
// 提取所有好友ID
|
||||
$friendIds = array_column($list, 'id');
|
||||
|
||||
/* // 一次性查询所有好友的未读消息数量
|
||||
$unreadCounts = [];
|
||||
if (!empty($friendIds)) {
|
||||
$unreadResults = Db::table('s2_wechat_message')
|
||||
->field('wechatFriendId, COUNT(*) as count')
|
||||
->where('wechatFriendId', 'in', $friendIds)
|
||||
->where('isRead', 0)
|
||||
->group('wechatFriendId')
|
||||
->select();
|
||||
if (!empty($unreadResults)) {
|
||||
foreach ($unreadResults as $result) {
|
||||
$unreadCounts[$result['wechatFriendId']] = $result['count'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 一次性查询所有好友的最新消息
|
||||
$latestMessages = [];
|
||||
if (!empty($friendIds)) {
|
||||
// 使用子查询获取每个好友的最新消息ID
|
||||
$subQuery = Db::table('s2_wechat_message')
|
||||
->field('MAX(id) as max_id, wechatFriendId')
|
||||
->where('wechatFriendId', 'in', $friendIds)
|
||||
->group('wechatFriendId')
|
||||
->buildSql();
|
||||
|
||||
if (!empty($subQuery)) {
|
||||
// 查询最新消息的详细信息
|
||||
$messageResults = Db::table('s2_wechat_message')
|
||||
->alias('m')
|
||||
->join([$subQuery => 'sub'], 'm.id = sub.max_id')
|
||||
->field('m.*, sub.wechatFriendId')
|
||||
->select();
|
||||
|
||||
if (!empty($messageResults)) {
|
||||
foreach ($messageResults as $message) {
|
||||
$latestMessages[$message['wechatFriendId']] = $message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
$aiTypeData = [];
|
||||
if (!empty($friendIds)) {
|
||||
$aiTypeData = FriendSettings::where('friendId', 'in', $friendIds)->column('friendId,type');
|
||||
@@ -83,18 +39,6 @@ class WechatFriendController extends BaseController
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
|
||||
$v['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s', $v['passTime']) : '';
|
||||
|
||||
|
||||
/* $config = [
|
||||
'unreadCount' => isset($unreadCounts[$v['id']]) ? $unreadCounts[$v['id']] : 0,
|
||||
'chat' => isset($latestMessages[$v['id']]),
|
||||
'msgTime' => isset($latestMessages[$v['id']]) ? $latestMessages[$v['id']]['wechatTime'] : 0
|
||||
];
|
||||
|
||||
// 将消息配置添加到好友数据中
|
||||
$v['config'] = $config;*/
|
||||
|
||||
|
||||
$v['aiType'] = isset($aiTypeData[$v['id']]) ? $aiTypeData[$v['id']] : 0;
|
||||
}
|
||||
unset($v);
|
||||
@@ -141,4 +85,85 @@ class WechatFriendController extends BaseController
|
||||
|
||||
return ResponseHelper::success(['detail' => $friend]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新好友资料(公司、姓名、手机号等字段可单独更新)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateFriendInfo()
|
||||
{
|
||||
$friendId = $this->request->param('id');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($friendId)) {
|
||||
return ResponseHelper::error('好友ID不能为空');
|
||||
}
|
||||
|
||||
$friend = Db::table('s2_wechat_friend')
|
||||
->where(['id' => $friendId, 'accountId' => $accountId, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在或无权限操作');
|
||||
}
|
||||
|
||||
$requestData = $this->request->param();
|
||||
$updatableColumns = [
|
||||
'phone',
|
||||
];
|
||||
$columnUpdates = [];
|
||||
|
||||
foreach ($updatableColumns as $field) {
|
||||
if (array_key_exists($field, $requestData)) {
|
||||
$columnUpdates[$field] = $requestData[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$extendFieldsData = [];
|
||||
if (!empty($friend['extendFields'])) {
|
||||
$decodedExtend = json_decode($friend['extendFields'], true);
|
||||
$extendFieldsData = is_array($decodedExtend) ? $decodedExtend : [];
|
||||
}
|
||||
|
||||
$extendFieldKeys = [
|
||||
'company',
|
||||
'name',
|
||||
'position',
|
||||
'email',
|
||||
'address',
|
||||
'wechat',
|
||||
'qq',
|
||||
'remark'
|
||||
];
|
||||
$extendFieldsUpdated = false;
|
||||
|
||||
foreach ($extendFieldKeys as $key) {
|
||||
if (array_key_exists($key, $requestData)) {
|
||||
$extendFieldsData[$key] = $requestData[$key];
|
||||
$extendFieldsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($extendFieldsUpdated) {
|
||||
$columnUpdates['extendFields'] = json_encode($extendFieldsData, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
if (empty($columnUpdates)) {
|
||||
return ResponseHelper::error('没有可更新的字段');
|
||||
}
|
||||
|
||||
$columnUpdates['updateTime'] = time();
|
||||
|
||||
try {
|
||||
Db::table('s2_wechat_friend')->where('id', $friendId)->update($columnUpdates);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['id' => $friendId]);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class Attachment extends Controller
|
||||
// 验证文件
|
||||
$validate = \think\facade\Validate::rule([
|
||||
'file' => [
|
||||
'fileSize' => 10485760, // 10MB
|
||||
'fileSize' => 50485760, // 50MB
|
||||
'fileExt' => 'jpg,jpeg,png,gif,doc,docx,pdf,zip,rar,mp4,mp3,csv,xlsx,xls,ppt,pptx,txt',
|
||||
]
|
||||
]);
|
||||
|
||||
@@ -9,7 +9,7 @@ class User extends Model
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
const ADMIN_STP = 1; // 主操盘手账号
|
||||
const ADMIN_STP = 1; // 操盘手账号
|
||||
const ADMIN_OTP = 0;
|
||||
const NOT_USER = -1; // 非登录用户用于任务操作的(S2系统专属)
|
||||
const MASTER_USER = 1; // 操盘手
|
||||
|
||||
@@ -110,6 +110,9 @@ Route::group('v1/', function () {
|
||||
Route::get('getJdSocialMedia', 'app\cunkebao\controller\WorkbenchController@getJdSocialMedia'); // 获取京东联盟导购媒体
|
||||
Route::get('getJdPromotionSite', 'app\cunkebao\controller\WorkbenchController@getJdPromotionSite'); // 获取京东联盟广告位
|
||||
Route::get('changeLink', 'app\cunkebao\controller\WorkbenchController@changeLink'); // 获取京东联盟广告位
|
||||
|
||||
Route::get('group-push-stats', 'app\cunkebao\controller\WorkbenchController@getGroupPushStats'); // 获取群发统计数据
|
||||
Route::get('group-push-history', 'app\cunkebao\controller\WorkbenchController@getGroupPushHistory'); // 获取推送历史记录列表
|
||||
});
|
||||
|
||||
// 内容库相关
|
||||
|
||||
@@ -111,10 +111,16 @@ class ContentLibraryController extends Controller
|
||||
$sourceType = $this->request->param('sourceType', ''); // 新增:来源类型,1=好友,2=群
|
||||
|
||||
$where = [
|
||||
['userId', '=', $this->request->userInfo['id']],
|
||||
['companyId' , '=', $this->request->userInfo['companyId']],
|
||||
['isDel', '=', 0] // 只查询未删除的记录
|
||||
];
|
||||
|
||||
if(empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['userId', '=', $this->request->userInfo['id']];
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 添加名称模糊搜索
|
||||
if ($keyword !== '') {
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
@@ -307,11 +313,18 @@ class ContentLibraryController extends Controller
|
||||
return json(['code' => 400, 'msg' => '内容库名称不能为空']);
|
||||
}
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId' , '=', $this->request->userInfo['companyId']],
|
||||
['isDel', '=', 0] // 只查询未删除的记录
|
||||
];
|
||||
|
||||
if(empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['userId', '=', $this->request->userInfo['id']];
|
||||
}
|
||||
|
||||
// 查询内容库是否存在
|
||||
$library = ContentLibrary::where([
|
||||
['id', '=', $param['id']],
|
||||
['userId', '=', $this->request->userInfo['id']]
|
||||
])->find();
|
||||
$library = ContentLibrary::where($where)->find();
|
||||
|
||||
if (!$library) {
|
||||
return json(['code' => 500, 'msg' => '内容库不存在']);
|
||||
@@ -766,16 +779,20 @@ class ContentLibraryController extends Controller
|
||||
$content = Request::param('content', '');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
// 简单验证
|
||||
if (empty($id)) {
|
||||
if (empty($id) && empty($content)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 查询内容项目是否存在并检查权限
|
||||
$item = ContentItem::alias('ci')
|
||||
->join('content_library cl', 'ci.libraryId = cl.id')
|
||||
->where(['ci.id' => $id, 'ci.isDel' => 0, 'cl.isDel' => 0, 'cl.companyId' => $companyId])
|
||||
->field('ci.*')
|
||||
->find();
|
||||
if(!empty($id)) {
|
||||
// 查询内容项目是否存在并检查权限
|
||||
$item = ContentItem::alias('ci')
|
||||
->join('content_library cl', 'ci.libraryId = cl.id')
|
||||
->where(['ci.id' => $id, 'ci.isDel' => 0, 'cl.isDel' => 0, 'cl.companyId' => $companyId])
|
||||
->field('ci.*')
|
||||
->find();
|
||||
}else{
|
||||
$item['content'] = $content;
|
||||
}
|
||||
|
||||
if (empty($item)) {
|
||||
return json(['code' => 500, 'msg' => '内容项目不存在或无权限操作']);
|
||||
|
||||
@@ -25,13 +25,18 @@ class StatsController extends Controller
|
||||
*/
|
||||
public function baseInfoStats()
|
||||
{
|
||||
$deviceNum = Db::name('device')->where(['companyId' => $this->request->userInfo['companyId'], 'deleteTime' => 0])->count();
|
||||
$wechatNum = Db::name('wechat_customer')->where(['companyId' => $this->request->userInfo['companyId']])->count();
|
||||
$aliveWechatNum = Db::name('wechat_customer')->alias('wc')
|
||||
->join('device_wechat_login dwl', 'wc.wechatId = dwl.wechatId')
|
||||
->where(['wc.companyId' => $this->request->userInfo['companyId'], 'dwl.alive' => 1])
|
||||
->group('wc.wechatId')
|
||||
->count();
|
||||
|
||||
$where = [
|
||||
['departmentId','=',$this->request->userInfo['companyId']]
|
||||
];
|
||||
if (!empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['id','=',$this->request->userInfo['s2_accountId']];
|
||||
}
|
||||
$accounts = Db::table('s2_company_account')->where($where)->column('id');
|
||||
|
||||
$deviceNum = Db::table('s2_device')->whereIn('currentAccountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$wechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->count();
|
||||
$aliveWechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->where(['wechatAlive' => 1])->count();
|
||||
$data = [
|
||||
'deviceNum' => $deviceNum,
|
||||
'wechatNum' => $wechatNum,
|
||||
@@ -58,29 +63,50 @@ class StatsController extends Controller
|
||||
->page(1, $num)
|
||||
->select();
|
||||
|
||||
foreach ($planScene as &$v) {
|
||||
$allNum = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.sceneId' => $v['id'], 'ac.companyId' => $this->request->userInfo['companyId'], 'ac.deleteTime' => 0])
|
||||
->count();
|
||||
|
||||
$addNum = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.sceneId' => $v['id'], 'ac.companyId' => $this->request->userInfo['companyId'], 'ac.deleteTime' => 0])
|
||||
->whereIn('tc.status', [1, 2, 3, 4])
|
||||
->count();
|
||||
|
||||
$passNum = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.sceneId' => $v['id'], 'ac.companyId' => $this->request->userInfo['companyId'], 'ac.deleteTime' => 0])
|
||||
->whereIn('tc.status', [4])
|
||||
->count();
|
||||
|
||||
$v['allNum'] = $allNum;
|
||||
$v['addNum'] = $addNum;
|
||||
$v['passNum'] = $passNum;
|
||||
if (empty($planScene)) {
|
||||
return successJson([], '获取成功');
|
||||
}
|
||||
unset($v);
|
||||
|
||||
$sceneIds = array_column($planScene, 'id');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
|
||||
$stats = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where([
|
||||
['ac.companyId', '=', $companyId],
|
||||
['ac.deleteTime', '=', 0],
|
||||
['ac.sceneId', 'in', $sceneIds],
|
||||
])
|
||||
->field([
|
||||
'ac.sceneId',
|
||||
Db::raw('COUNT(1) as allNum'),
|
||||
Db::raw("SUM(CASE WHEN tc.status IN (1,2,3,4) THEN 1 ELSE 0 END) as addNum"),
|
||||
Db::raw("SUM(CASE WHEN tc.status = 4 THEN 1 ELSE 0 END) as passNum"),
|
||||
])
|
||||
->group('ac.sceneId')
|
||||
->select();
|
||||
|
||||
$statsMap = [];
|
||||
foreach ($stats as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$statsMap[$sceneId] = [
|
||||
'allNum' => (int)(is_array($row) ? ($row['allNum'] ?? 0) : ($row->allNum ?? 0)),
|
||||
'addNum' => (int)(is_array($row) ? ($row['addNum'] ?? 0) : ($row->addNum ?? 0)),
|
||||
'passNum' => (int)(is_array($row) ? ($row['passNum'] ?? 0) : ($row->passNum ?? 0)),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($planScene as &$item) {
|
||||
$sceneStats = $statsMap[$item['id']] ?? ['allNum' => 0, 'addNum' => 0, 'passNum' => 0];
|
||||
$item['allNum'] = $sceneStats['allNum'];
|
||||
$item['addNum'] = $sceneStats['addNum'];
|
||||
$item['passNum'] = $sceneStats['passNum'];
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return successJson($planScene, '获取成功');
|
||||
}
|
||||
|
||||
@@ -150,44 +176,62 @@ class StatsController extends Controller
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$days = 7;
|
||||
|
||||
$dates = [];
|
||||
$endTime = strtotime(date('Y-m-d 23:59:59'));
|
||||
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime('-' . ($days - 1) . ' day')));
|
||||
|
||||
$dateMap = [];
|
||||
$dateLabels = [];
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$currentDate = date('Y-m-d', strtotime("-" . ($days - 1 - $i) . " day"));
|
||||
$weekIndex = date("w", strtotime($currentDate));
|
||||
$dateMap[$currentDate] = self::WEEK[$weekIndex];
|
||||
$dateLabels[] = self::WEEK[$weekIndex];
|
||||
}
|
||||
|
||||
$baseWhere = [
|
||||
['ac.companyId', '=', $companyId],
|
||||
['ac.deleteTime', '=', 0],
|
||||
];
|
||||
|
||||
$fetchCounts = function (string $timeField, array $status = []) use ($baseWhere, $startTime, $endTime) {
|
||||
$query = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where($baseWhere)
|
||||
->whereBetween('tc.' . $timeField, [$startTime, $endTime]);
|
||||
if (!empty($status)) {
|
||||
$query->whereIn('tc.status', $status);
|
||||
}
|
||||
$rows = $query->field([
|
||||
"FROM_UNIXTIME(tc.{$timeField}, '%Y-%m-%d')" => 'day',
|
||||
'COUNT(1)' => 'total'
|
||||
])->group('day')->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$day = is_array($row) ? ($row['day'] ?? '') : ($row->day ?? '');
|
||||
$total = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
if ($day) {
|
||||
$result[$day] = $total;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$allNumDict = $fetchCounts('createTime');
|
||||
$addNumDict = $fetchCounts('updateTime', [1, 2, 3, 4]);
|
||||
$passNumDict = $fetchCounts('updateTime', [4]);
|
||||
|
||||
$allNum = [];
|
||||
$addNum = [];
|
||||
$passNum = [];
|
||||
|
||||
for ($i = $days - 1; $i >= 0; $i--) {
|
||||
$date = date('Y-m-d', strtotime("-$i day"));
|
||||
$start = strtotime($date . ' 00:00:00');
|
||||
$end = strtotime($date . ' 23:59:59');
|
||||
|
||||
// 获客总量
|
||||
$allNum[] = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.companyId' => $companyId, 'ac.deleteTime' => 0])
|
||||
->where('tc.createTime', 'between', [$start, $end])
|
||||
->count();
|
||||
|
||||
// 添加量
|
||||
$addNum[] = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.companyId' => $companyId, 'ac.deleteTime' => 0])
|
||||
->where('tc.updateTime', 'between', [$start, $end])
|
||||
->whereIn('tc.status', [1, 2, 3, 4])
|
||||
->count();
|
||||
|
||||
// 通过量
|
||||
$passNum[] = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.companyId' => $companyId, 'ac.deleteTime' => 0])
|
||||
->where('tc.updateTime', 'between', [$start, $end])
|
||||
->whereIn('tc.status', [4])
|
||||
->count();
|
||||
|
||||
$week = date("w", strtotime($date));
|
||||
$dates[] = self::WEEK[$week];
|
||||
foreach (array_keys($dateMap) as $dateKey) {
|
||||
$allNum[] = $allNumDict[$dateKey] ?? 0;
|
||||
$addNum[] = $addNumDict[$dateKey] ?? 0;
|
||||
$passNum[] = $passNumDict[$dateKey] ?? 0;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'date' => $dates,
|
||||
'date' => $dateLabels,
|
||||
'allNum' => $allNum,
|
||||
'addNum' => $addNum,
|
||||
'passNum' => $passNum,
|
||||
@@ -360,19 +404,26 @@ class StatsController extends Controller
|
||||
$isAdmin = $this->request->userInfo['isAdmin'];
|
||||
|
||||
|
||||
$device = Db::name('device')->where(['companyId' => $companyId,'deleteTime' => 0]);
|
||||
$wechat = Db::name('wechat_customer')->where(['companyId' => $companyId]);
|
||||
$contentLibrary = Db::name('content_library')->where(['companyId' => $companyId,'isDel' => 0]);
|
||||
$user = Db::name('wechat_friendship')->where(['companyId' => $companyId,'deleteTime' => 0]);
|
||||
$where = [
|
||||
['departmentId','=',$companyId]
|
||||
];
|
||||
if (!empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['id','=',$this->request->userInfo['s2_accountId']];
|
||||
}
|
||||
$accounts = Db::table('s2_company_account')->where($where)->column('id');
|
||||
|
||||
|
||||
$userNum = Db::table('s2_wechat_friend')->whereIn('accountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$deviceNum = Db::table('s2_device')->whereIn('currentAccountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$wechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->count();
|
||||
|
||||
|
||||
$contentLibrary = Db::name('content_library')->where(['companyId' => $companyId,'isDel' => 0]);
|
||||
if(empty($isAdmin)){
|
||||
$contentLibrary = $contentLibrary->where(['userId' => $userId]);
|
||||
}
|
||||
|
||||
$deviceNum = $device->count();
|
||||
$wechatNum = $wechat->count();
|
||||
$contentLibraryNum = $contentLibrary->count();
|
||||
$userNum = $user->count();
|
||||
|
||||
|
||||
$data = [
|
||||
'deviceNum' => $deviceNum,
|
||||
|
||||
@@ -116,13 +116,13 @@ class TokensController extends BaseController
|
||||
$res = $paymentService->queryOrder($orderNo);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200) {
|
||||
return ResponseHelper::success('', '订单已支付');
|
||||
return ResponseHelper::success($order, '订单已支付');
|
||||
} else {
|
||||
$errorMsg = !empty($order['payInfo']) ? $order['payInfo'] : '订单未支付';
|
||||
return ResponseHelper::error($errorMsg);
|
||||
return ResponseHelper::success($order,$errorMsg,400);
|
||||
}
|
||||
} else {
|
||||
return ResponseHelper::success('', '订单已支付');
|
||||
return ResponseHelper::success($order, '订单已支付');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,17 +38,23 @@ class GetPlanSceneListV1Controller extends BaseController
|
||||
// 查询数据
|
||||
$query = PlansSceneModel::where($where);
|
||||
|
||||
// 获取总数
|
||||
$total = $query->count();
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->order('sort DESC')->select()->toArray();
|
||||
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sceneIds = array_column($list, 'id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$statsMap = $this->buildSceneStats($sceneIds, (int)$companyId);
|
||||
|
||||
// 处理数据
|
||||
foreach($list as &$val) {
|
||||
$val['scenarioTags'] = json_decode($val['scenarioTags'], true);
|
||||
$val['count'] = $this->getPlanCount($val['id']);
|
||||
$val['growth'] = $this->calculateGrowth($val['id']);
|
||||
$val['scenarioTags'] = json_decode($val['scenarioTags'], true) ?: [];
|
||||
$sceneStats = $statsMap[$val['id']] ?? ['count' => 0, 'growth' => '0%'];
|
||||
$val['count'] = $sceneStats['count'];
|
||||
$val['growth'] = $sceneStats['growth'];
|
||||
}
|
||||
unset($val);
|
||||
|
||||
@@ -97,7 +103,7 @@ class GetPlanSceneListV1Controller extends BaseController
|
||||
return ResponseHelper::error('场景不存在');
|
||||
}
|
||||
|
||||
$data['scenarioTags'] = json_decode($data['scenarioTags'], true);
|
||||
$data['scenarioTags'] = json_decode($data['scenarioTags'], true) ?: [];
|
||||
$data['count'] = $this->getPlanCount($id);
|
||||
$data['growth'] = $this->calculateGrowth($id);
|
||||
|
||||
@@ -130,27 +136,151 @@ class GetPlanSceneListV1Controller extends BaseController
|
||||
*/
|
||||
private function calculateGrowth(int $sceneId): string
|
||||
{
|
||||
// 获取本月和上月的计划数量
|
||||
$currentMonth = Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('status', 1)
|
||||
->whereTime('createTime', '>=', strtotime(date('Y-m-01')))
|
||||
->count();
|
||||
|
||||
$lastMonth = Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('status', 1)
|
||||
->whereTime('createTime', 'between', [
|
||||
strtotime(date('Y-m-01', strtotime('-1 month'))),
|
||||
strtotime(date('Y-m-01')) - 1
|
||||
])
|
||||
->count();
|
||||
|
||||
if ($lastMonth == 0) {
|
||||
return $currentMonth > 0 ? '100%' : '0%';
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
return $this->formatGrowthPercentage($currentMonth, $lastMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量构建场景统计数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function buildSceneStats(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$growth = round(($currentMonth - $lastMonth) / $lastMonth * 100, 2);
|
||||
|
||||
$totalCounts = $this->getSceneTaskCounts($sceneIds, $companyId);
|
||||
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
$stats = [];
|
||||
foreach ($sceneIds as $sceneId) {
|
||||
$current = $currentMonthCounts[$sceneId] ?? 0;
|
||||
$last = $lastMonthCounts[$sceneId] ?? 0;
|
||||
$stats[$sceneId] = [
|
||||
'count' => $totalCounts[$sceneId] ?? 0,
|
||||
'growth' => $this->formatGrowthPercentage($current, $last),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景计划总数
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneTaskCounts(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->whereIn('sceneId', $sceneIds)
|
||||
->where('companyId', $companyId)
|
||||
->where('deleteTime', 0)
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景月度数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneMonthlyCounts(array $sceneIds, int $companyId, int $startTime, int $endTime): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->whereIn('sceneId', $sceneIds)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个场景的月度数据
|
||||
* @param int $sceneId
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return int
|
||||
*/
|
||||
private function getSceneMonthlyCount(int $sceneId, int $companyId, int $startTime, int $endTime): int
|
||||
{
|
||||
return Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算增长百分比
|
||||
* @param int $current
|
||||
* @param int $last
|
||||
* @return string
|
||||
*/
|
||||
private function formatGrowthPercentage(int $current, int $last): string
|
||||
{
|
||||
if ($last == 0) {
|
||||
return $current > 0 ? '100%' : '0%';
|
||||
}
|
||||
|
||||
$growth = round(($current - $last) / $last * 100, 2);
|
||||
return $growth . '%';
|
||||
}
|
||||
}
|
||||
@@ -50,29 +50,35 @@ class PlanSceneV1Controller extends BaseController
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
if (!empty($list)) {
|
||||
$taskIds = array_column($list, 'id');
|
||||
$statsMap = $this->buildTaskStats($taskIds);
|
||||
|
||||
foreach($list as &$val){
|
||||
$val['createTime'] = date('Y-m-d H:i:s', $val['createTime']);
|
||||
$val['updateTime'] = date('Y-m-d H:i:s', $val['updateTime']);
|
||||
$val['sceneConf'] = json_decode($val['sceneConf'],true);
|
||||
$val['reqConf'] = json_decode($val['reqConf'],true);
|
||||
$val['msgConf'] = json_decode($val['msgConf'],true);
|
||||
$val['tagConf'] = json_decode($val['tagConf'],true);
|
||||
|
||||
$val['acquiredCount'] = Db::name('task_customer')->where('task_id',$val['id'])->count();
|
||||
$val['addedCount'] = Db::name('task_customer')->where('task_id',$val['id'])->whereIn('status',[1,2,3,4])->count();
|
||||
$val['passCount'] = Db::name('task_customer')->where('task_id',$val['id'])->where('status',4)->count();
|
||||
$val['passRate'] = 0;
|
||||
if(!empty($val['passCount']) && !empty($val['addedCount'])){
|
||||
$passRate = ($val['passCount'] / $val['addedCount']) * 100;
|
||||
$val['passRate'] = number_format($passRate,2);
|
||||
}
|
||||
|
||||
$lastTime = Db::name('task_customer')->where(['task_id'=>$val['id']])->max('updateTime');
|
||||
$val['lastUpdated'] = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--';
|
||||
$val['createTime'] = !empty($val['createTime']) ? date('Y-m-d H:i:s', $val['createTime']) : '';
|
||||
$val['updateTime'] = !empty($val['updateTime']) ? date('Y-m-d H:i:s', $val['updateTime']) : '';
|
||||
$val['sceneConf'] = json_decode($val['sceneConf'],true) ?: [];
|
||||
$val['reqConf'] = json_decode($val['reqConf'],true) ?: [];
|
||||
$val['msgConf'] = json_decode($val['msgConf'],true) ?: [];
|
||||
$val['tagConf'] = json_decode($val['tagConf'],true) ?: [];
|
||||
|
||||
$stats = $statsMap[$val['id']] ?? [
|
||||
'acquiredCount' => 0,
|
||||
'addedCount' => 0,
|
||||
'passCount' => 0,
|
||||
'lastUpdated' => 0
|
||||
];
|
||||
|
||||
$val['acquiredCount'] = $stats['acquiredCount'];
|
||||
$val['addedCount'] = $stats['addedCount'];
|
||||
$val['passCount'] = $stats['passCount'];
|
||||
$val['passRate'] = ($stats['addedCount'] > 0 && $stats['passCount'] > 0)
|
||||
? number_format(($stats['passCount'] / $stats['addedCount']) * 100, 2)
|
||||
: 0;
|
||||
$val['lastUpdated'] = !empty($stats['lastUpdated']) ? date('Y-m-d H:i', $stats['lastUpdated']) : '--';
|
||||
}
|
||||
unset($val);
|
||||
}
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list
|
||||
@@ -383,6 +389,46 @@ class PlanSceneV1Controller extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务统计
|
||||
* @param array $taskIds
|
||||
* @return array
|
||||
*/
|
||||
private function buildTaskStats(array $taskIds): array
|
||||
{
|
||||
if (empty($taskIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('task_customer')
|
||||
->whereIn('task_id', $taskIds)
|
||||
->field([
|
||||
'task_id as taskId',
|
||||
'COUNT(1) as acquiredCount',
|
||||
"SUM(CASE WHEN status IN (1,2,3,4) THEN 1 ELSE 0 END) as addedCount",
|
||||
"SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) as passCount",
|
||||
'MAX(updateTime) as lastUpdated'
|
||||
])
|
||||
->group('task_id')
|
||||
->select();
|
||||
|
||||
$stats = [];
|
||||
foreach ($rows as $row) {
|
||||
$taskId = is_array($row) ? ($row['taskId'] ?? 0) : ($row->taskId ?? 0);
|
||||
if (!$taskId) {
|
||||
continue;
|
||||
}
|
||||
$stats[$taskId] = [
|
||||
'acquiredCount' => (int)(is_array($row) ? ($row['acquiredCount'] ?? 0) : ($row->acquiredCount ?? 0)),
|
||||
'addedCount' => (int)(is_array($row) ? ($row['addedCount'] ?? 0) : ($row->addedCount ?? 0)),
|
||||
'passCount' => (int)(is_array($row) ? ($row['passCount'] ?? 0) : ($row->passCount ?? 0)),
|
||||
'lastUpdated' => (int)(is_array($row) ? ($row['lastUpdated'] ?? 0) : ($row->lastUpdated ?? 0)),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取微信小程序码
|
||||
|
||||
@@ -148,11 +148,11 @@ class PosterWeChatMiniProgram extends Controller
|
||||
{
|
||||
|
||||
$taskId = request()->param('id');
|
||||
$phoneData = request()->param('phone');
|
||||
if (!$phoneData) {
|
||||
$rawInput = trim((string)request()->param('phone', ''));
|
||||
if ($rawInput === '') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '手机号不能为空'
|
||||
'message' => '手机号或微信号不能为空'
|
||||
]);
|
||||
}
|
||||
$task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
|
||||
@@ -165,31 +165,69 @@ class PosterWeChatMiniProgram extends Controller
|
||||
}
|
||||
|
||||
|
||||
$phoneData = explode("\n", $phoneData);
|
||||
foreach ($phoneData as $phone) {
|
||||
if (empty($phone)) {
|
||||
$lines = preg_split('/\r\n|\r|\n/', $rawInput);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $phone)->find();
|
||||
$parts = array_map('trim', explode(',', $line, 2));
|
||||
$identifier = $parts[0] ?? '';
|
||||
$remark = $parts[1] ?? '';
|
||||
if ($identifier === '') {
|
||||
continue;
|
||||
}
|
||||
$isPhone = preg_match('/^\+?\d{6,}$/', $identifier);
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $identifier)->find();
|
||||
if (!$trafficPool) {
|
||||
Db::name('traffic_pool')->insert([
|
||||
'identifier' => $phone,
|
||||
'mobile' => $phone,
|
||||
$insertData = [
|
||||
'identifier' => $identifier,
|
||||
'createTime' => time()
|
||||
]);
|
||||
];
|
||||
if ($isPhone) {
|
||||
$insertData['mobile'] = $identifier;
|
||||
} else {
|
||||
$insertData['wechatId'] = $identifier;
|
||||
}
|
||||
Db::name('traffic_pool')->insert($insertData);
|
||||
} else {
|
||||
$updates = [];
|
||||
if ($isPhone && empty($trafficPool['mobile'])) {
|
||||
$updates['mobile'] = $identifier;
|
||||
}
|
||||
if (!$isPhone && empty($trafficPool['wechatId'])) {
|
||||
$updates['wechatId'] = $identifier;
|
||||
}
|
||||
if (!empty($updates)) {
|
||||
$updates['updateTime'] = time();
|
||||
Db::name('traffic_pool')->where('id', $trafficPool['id'])->update($updates);
|
||||
}
|
||||
}
|
||||
|
||||
$taskCustomer = Db::name('task_customer')->where('task_id', $taskId)->where('phone', $phone)->find();
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $taskId)
|
||||
->where('phone', $identifier)
|
||||
->find();
|
||||
if (empty($taskCustomer)) {
|
||||
Db::name('task_customer')->insert([
|
||||
$insertCustomer = [
|
||||
'task_id' => $taskId,
|
||||
// 'identifier' => $phone,
|
||||
'phone' => $phone,
|
||||
'phone' => $identifier,
|
||||
'source' => $task['name'],
|
||||
'createTime' => time(),
|
||||
'tags' => json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
]);
|
||||
];
|
||||
if ($remark !== '') {
|
||||
$insertCustomer['remark'] = $remark;
|
||||
}
|
||||
Db::name('task_customer')->insert($insertCustomer);
|
||||
} elseif ($remark !== '' && $taskCustomer['remark'] !== $remark) {
|
||||
Db::name('task_customer')
|
||||
->where('id', $taskCustomer['id'])
|
||||
->update([
|
||||
'remark' => $remark,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,92 +19,6 @@ use think\Db;
|
||||
*/
|
||||
class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 计算今日可添加好友数量
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getCanAddFriendCount(string $wechatId): int
|
||||
{
|
||||
$weight = (string)WechatCustomerModel::where(
|
||||
[
|
||||
'wechatId' => $wechatId,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->value('weight');
|
||||
|
||||
return json_decode($weight)->addLimit ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算今日新增好友数量
|
||||
*
|
||||
* @param string $ownerWechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriendCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendShipModel::where(compact('ownerWechatId'))
|
||||
->whereBetween('createTime',
|
||||
[
|
||||
strtotime(date('Y-m-d 00:00:00')),
|
||||
strtotime(date('Y-m-d 23:59:59'))
|
||||
]
|
||||
)
|
||||
->count('*');
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 获取微信加友状态
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getWechatAddFriendStatus(string $wechatId): int
|
||||
{
|
||||
return Db::name('device_wechat_login')->where(['wechatId' => $wechatId])->order('id DESC')->value('alive');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信好友数量
|
||||
*
|
||||
* @param string $ownerWechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getFriendsCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendShipModel::where(
|
||||
[
|
||||
'ownerWechatId' => $ownerWechatId,
|
||||
//'companyId' => $this->getUserInfo('companyId'),
|
||||
]
|
||||
)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信所属设备
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getWhereOnDevice(string $wechatId): string
|
||||
{
|
||||
return (string)DeviceModel::alias('d')
|
||||
->where(
|
||||
[
|
||||
'l.wechatId' => $wechatId,
|
||||
'l.alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
|
||||
'l.companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->join('device_wechat_login l', 'd.id = l.deviceId AND l.companyId = '. $this->getUserInfo('companyId'))
|
||||
->order('l.id desc')
|
||||
->value('d.memo');
|
||||
}
|
||||
|
||||
/**
|
||||
* 主操盘手获取项目下所有设备的id
|
||||
*
|
||||
@@ -172,22 +86,6 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 获取设备最新活跃时间
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getLatestActiveTime(string $wechatId): string
|
||||
{
|
||||
$wechatAccountId = Db::table('s2_wechat_account')->where(['wechatId' => $wechatId])->value('id');
|
||||
if (empty($wechatAccountId)){
|
||||
return '-';
|
||||
}
|
||||
$time = Db::table('s2_wechat_message')->where('wechatAccountId',$wechatAccountId)->order('id DESC')->value('wechatTime');
|
||||
return !empty($time) ? date('Y-m-d H:i:s', $time) : '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
@@ -256,15 +154,26 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
protected function makeResultedSet(\think\Paginator $result): array
|
||||
{
|
||||
$resultSets = [];
|
||||
$items = $result->items();
|
||||
|
||||
foreach ($result->items() as $item) {
|
||||
if (empty($items)) {
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
$wechatIds = array_values(array_unique(array_map(function ($item) {
|
||||
return $item->wechatId ?? ($item['wechatId'] ?? '');
|
||||
}, $items)));
|
||||
|
||||
$metrics = $this->collectWechatMetrics($wechatIds);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$sections = $item->toArray() + [
|
||||
'times' => $this->getCanAddFriendCount($item->wechatId),
|
||||
'addedCount' => $this->getTodayNewFriendCount($item->wechatId),
|
||||
'wechatStatus' => $this->getWechatAddFriendStatus($item->wechatId),
|
||||
'totalFriend' => $this->getFriendsCount($item->wechatId),
|
||||
'deviceMemo' => $this->getWhereOnDevice($item->wechatId),
|
||||
'activeTime' => $this->getLatestActiveTime($item->wechatId),
|
||||
'times' => $metrics['addLimit'][$item->wechatId] ?? 0,
|
||||
'addedCount' => $metrics['todayAdded'][$item->wechatId] ?? 0,
|
||||
'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0,
|
||||
'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0,
|
||||
'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '',
|
||||
'activeTime' => $metrics['activeTime'][$item->wechatId] ?? '-',
|
||||
];
|
||||
|
||||
array_push($resultSets, $sections);
|
||||
@@ -273,6 +182,109 @@ class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量收集微信账号的统计信息
|
||||
* @param array $wechatIds
|
||||
* @return array
|
||||
*/
|
||||
protected function collectWechatMetrics(array $wechatIds): array
|
||||
{
|
||||
$metrics = [
|
||||
'addLimit' => [],
|
||||
'todayAdded' => [],
|
||||
'totalFriend' => [],
|
||||
'wechatStatus' => [],
|
||||
'deviceMemo' => [],
|
||||
'activeTime' => [],
|
||||
];
|
||||
|
||||
if (empty($wechatIds)) {
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 可添加好友额度
|
||||
$weightRows = WechatCustomerModel::where('companyId', $companyId)
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('weight', 'wechatId');
|
||||
foreach ($weightRows as $wechatId => $weight) {
|
||||
$decoded = json_decode($weight, true);
|
||||
$metrics['addLimit'][$wechatId] = $decoded['addLimit'] ?? 0;
|
||||
}
|
||||
|
||||
// 今日新增好友
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
$todayRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
->select();
|
||||
foreach ($todayRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
if ($wechatId) {
|
||||
$metrics['todayAdded'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
}
|
||||
|
||||
// 总好友
|
||||
$friendRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
|
||||
->field('ownerWechatId, COUNT(*) as total')
|
||||
->group('ownerWechatId')
|
||||
->select();
|
||||
foreach ($friendRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
|
||||
if ($wechatId) {
|
||||
$metrics['totalFriend'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
}
|
||||
|
||||
// 设备状态与备注
|
||||
$loginRows = Db::name('device_wechat_login')
|
||||
->alias('l')
|
||||
->leftJoin('device d', 'd.id = l.deviceId')
|
||||
->field('l.wechatId,l.alive,d.memo')
|
||||
->where('l.companyId', $companyId)
|
||||
->whereIn('l.wechatId', $wechatIds)
|
||||
->order('l.id', 'desc')
|
||||
->select();
|
||||
foreach ($loginRows as $row) {
|
||||
$wechatId = is_array($row) ? ($row['wechatId'] ?? '') : ($row->wechatId ?? '');
|
||||
if (empty($wechatId) || isset($metrics['wechatStatus'][$wechatId])) {
|
||||
continue;
|
||||
}
|
||||
$metrics['wechatStatus'][$wechatId] = (int)(is_array($row) ? ($row['alive'] ?? 0) : ($row->alive ?? 0));
|
||||
$metrics['deviceMemo'][$wechatId] = is_array($row) ? ($row['memo'] ?? '') : ($row->memo ?? '');
|
||||
}
|
||||
|
||||
// 活跃时间
|
||||
$accountMap = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('id', 'wechatId');
|
||||
if (!empty($accountMap)) {
|
||||
$accountRows = Db::table('s2_wechat_message')
|
||||
->whereIn('wechatAccountId', array_values($accountMap))
|
||||
->field('wechatAccountId, MAX(wechatTime) as lastTime')
|
||||
->group('wechatAccountId')
|
||||
->select();
|
||||
$accountLastTime = [];
|
||||
foreach ($accountRows as $row) {
|
||||
$accountId = is_array($row) ? ($row['wechatAccountId'] ?? 0) : ($row->wechatAccountId ?? 0);
|
||||
if ($accountId) {
|
||||
$accountLastTime[$accountId] = (int)(is_array($row) ? ($row['lastTime'] ?? 0) : ($row->lastTime ?? 0));
|
||||
}
|
||||
}
|
||||
foreach ($accountMap as $wechatId => $accountId) {
|
||||
if (isset($accountLastTime[$accountId]) && $accountLastTime[$accountId] > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $accountLastTime[$accountId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
*
|
||||
|
||||
@@ -69,7 +69,8 @@ class PostTransferFriends extends BaseController
|
||||
'greeting' => '您好,我是'. $wechat['nickname'] .'的辅助客服,请通过'
|
||||
];
|
||||
|
||||
$createAddFriendPlan = new PostCreateAddFriendPlanV1Controller();
|
||||
// 使用容器获取控制器实例,而不是直接实例化
|
||||
$createAddFriendPlan = app('app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller');
|
||||
|
||||
$taskId = Db::name('customer_acquisition_task')->insertGetId([
|
||||
'name' => '迁移好友('. $wechat['nickname'] .')',
|
||||
|
||||
@@ -39,16 +39,19 @@ class Workbench extends Validate
|
||||
// 群消息推送特有参数
|
||||
'pushType' => 'requireIf:type,3|in:0,1', // 推送方式 0定时 1立即
|
||||
'targetType' => 'requireIf:type,3|in:1,2', // 推送目标类型:1=群推送,2=好友推送
|
||||
'startTime' => 'requireIf:type,3|dateFormat:H:i',
|
||||
'endTime' => 'requireIf:type,3|dateFormat:H:i',
|
||||
'groupPushSubType' => 'checkGroupPushSubType|in:1,2', // 群推送子类型:1=群群发,2=群公告(仅当targetType=1时有效)
|
||||
'maxPerDay' => 'requireIf:type,3|number|min:1',
|
||||
'pushOrder' => 'requireIf:type,3|in:1,2', // 1最早 2最新
|
||||
'isLoop' => 'requireIf:type,3|in:0,1',
|
||||
'status' => 'requireIf:type,3|in:0,1',
|
||||
'wechatGroups' => 'checkGroupPushTarget|array|min:1', // 当targetType=1时必填
|
||||
'wechatFriends' => 'checkFriendPushTarget|array', // 当targetType=2时可选(可以为空)
|
||||
'deviceGroups' => 'checkFriendPushDevice|array|min:1', // 当targetType=2时必填
|
||||
'ownerWechatId' => 'checkFriendPushService', // 当targetType=2且未选择好友/流量池时必填
|
||||
'contentGroups' => 'requireIf:type,3|array|min:1',
|
||||
// 群公告特有参数
|
||||
'announcementContent' => 'checkAnnouncementContent|max:5000', // 群公告内容(当groupPushSubType=2时必填)
|
||||
'enableAiRewrite' => 'checkEnableAiRewrite|in:0,1', // 是否启用AI智能话术改写
|
||||
'aiRewritePrompt' => 'checkAiRewritePrompt|max:500', // AI改写提示词(当enableAiRewrite=1时必填)
|
||||
// 自动建群特有参数
|
||||
'groupNameTemplate' => 'requireIf:type,4|max:50',
|
||||
'maxGroupsPerDay' => 'requireIf:type,4|number|min:1',
|
||||
@@ -61,6 +64,7 @@ class Workbench extends Validate
|
||||
'accountGroups' => 'requireIf:type,5|array|min:1',
|
||||
// 通用参数
|
||||
'deviceGroups' => 'requireIf:type,1,2,5|array',
|
||||
'trafficPools' => 'checkFriendPushPools',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -123,13 +127,20 @@ class Workbench extends Validate
|
||||
'wechatGroups.checkGroupPushTarget' => '群推送时必须选择推送群组',
|
||||
'wechatGroups.array' => '推送群组格式错误',
|
||||
'wechatGroups.min' => '至少选择一个推送群组',
|
||||
'groupPushSubType.checkGroupPushSubType' => '群推送子类型错误',
|
||||
'groupPushSubType.in' => '群推送子类型只能是群群发或群公告',
|
||||
'announcementContent.checkAnnouncementContent' => '群公告必须输入公告内容',
|
||||
'announcementContent.max' => '公告内容最多5000个字符',
|
||||
'enableAiRewrite.checkEnableAiRewrite' => 'AI智能话术改写参数错误',
|
||||
'enableAiRewrite.in' => 'AI智能话术改写参数只能是0或1',
|
||||
'aiRewritePrompt.checkAiRewritePrompt' => '启用AI智能话术改写时,必须输入改写提示词',
|
||||
'aiRewritePrompt.max' => '改写提示词最多500个字符',
|
||||
'wechatFriends.requireIf' => '请选择推送好友',
|
||||
'wechatFriends.checkFriendPushTarget' => '好友推送时必须选择推送好友',
|
||||
'wechatFriends.array' => '推送好友格式错误',
|
||||
'deviceGroups.requireIf' => '请选择设备',
|
||||
'deviceGroups.checkFriendPushDevice' => '好友推送时必须选择设备',
|
||||
'deviceGroups.array' => '设备格式错误',
|
||||
'deviceGroups.min' => '至少选择一个设备',
|
||||
'ownerWechatId.checkFriendPushService' => '好友推送需选择客服或提供好友/流量池',
|
||||
// 自动建群相关提示
|
||||
'groupNameTemplate.requireIf' => '请设置群名称前缀',
|
||||
'groupNameTemplate.max' => '群名称前缀最多50个字符',
|
||||
@@ -160,6 +171,7 @@ class Workbench extends Validate
|
||||
'accountGroups.requireIf' => '流量分发时必须选择分发账号',
|
||||
'accountGroups.array' => '分发账号格式错误',
|
||||
'accountGroups.min' => '至少选择一个分发账号',
|
||||
'trafficPools.checkFriendPushPools' => '好友推送时请选择好友或流量池',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -169,7 +181,8 @@ class Workbench extends Validate
|
||||
'create' => ['name', 'type', 'autoStart', 'deviceGroups', 'targetGroups',
|
||||
'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes',
|
||||
'syncCount', 'syncType', 'accountGroups',
|
||||
'pushType', 'targetType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'wechatFriends', 'contentGroups',
|
||||
'pushType', 'targetType', 'groupPushSubType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'wechatFriends', 'trafficPools', 'ownerWechatId', 'contentGroups',
|
||||
'announcementContent', 'enableAiRewrite', 'aiRewritePrompt',
|
||||
'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax',
|
||||
'distributeType', 'timeType', 'accountGroups',
|
||||
],
|
||||
@@ -177,7 +190,8 @@ class Workbench extends Validate
|
||||
'update' => ['name', 'type', 'autoStart', 'deviceGroups', 'targetGroups',
|
||||
'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes',
|
||||
'syncCount', 'syncType', 'accountGroups',
|
||||
'pushType', 'targetType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'wechatFriends', 'deviceGroups', 'contentGroups',
|
||||
'pushType', 'targetType', 'groupPushSubType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'wechatGroups', 'wechatFriends', 'trafficPools', 'ownerWechatId', 'contentGroups',
|
||||
'announcementContent', 'enableAiRewrite', 'aiRewritePrompt',
|
||||
'groupNameTemplate', 'maxGroupsPerDay', 'groupSizeMin', 'groupSizeMax',
|
||||
'distributeType', 'timeType', 'accountGroups',
|
||||
]
|
||||
@@ -243,18 +257,126 @@ class Workbench extends Validate
|
||||
/**
|
||||
* 验证好友推送时设备必填(当targetType=2时,deviceGroups必填)
|
||||
*/
|
||||
protected function checkFriendPushDevice($value, $rule, $data)
|
||||
protected function checkFriendPushService($value, $rule, $data)
|
||||
{
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
if ($targetType == 2) {
|
||||
if ($value !== null && $value !== '' && !is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasFriends = isset($data['wechatFriends']) && is_array($data['wechatFriends']) && count($data['wechatFriends']) > 0;
|
||||
$hasPools = isset($data['trafficPools']) && is_array($data['trafficPools']) && count($data['trafficPools']) > 0;
|
||||
$hasServices = is_array($value) && count(array_filter($value, function ($item) {
|
||||
if (is_array($item)) {
|
||||
return !empty($item['ownerWechatId'] ?? $item['wechatId'] ?? $item['id']);
|
||||
}
|
||||
return $item !== null && $item !== '';
|
||||
})) > 0;
|
||||
|
||||
if (!$hasFriends && !$hasPools && !$hasServices) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证好友推送时是否选择好友或流量池(至少其一)
|
||||
*/
|
||||
protected function checkFriendPushPools($value, $rule, $data)
|
||||
{
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
if ($targetType == 2) {
|
||||
$hasFriends = isset($data['wechatFriends']) && !empty($data['wechatFriends']);
|
||||
$hasPools = isset($value) && $value !== null && $value !== '' && is_array($value) && count($value) > 0;
|
||||
if (!$hasFriends && !$hasPools) {
|
||||
return false;
|
||||
}
|
||||
if (isset($value) && $value !== null && $value !== '') {
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证群推送子类型(当targetType=1时,groupPushSubType必填且只能是1或2)
|
||||
*/
|
||||
protected function checkGroupPushSubType($value, $rule, $data)
|
||||
{
|
||||
// 如果是群消息推送类型
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
// 如果targetType=2(好友推送),则deviceGroups必填
|
||||
// 如果targetType=1(群推送),则groupPushSubType必填
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
if ($targetType == 2) {
|
||||
if ($targetType == 1) {
|
||||
// 检查值是否存在且有效
|
||||
if (!isset($value) || $value === null || $value === '') {
|
||||
if (!isset($value) || !in_array(intval($value), [1, 2])) {
|
||||
return false;
|
||||
}
|
||||
if (!is_array($value) || count($value) < 1) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证群公告内容(当groupPushSubType=2时,announcementContent必填)
|
||||
*/
|
||||
protected function checkAnnouncementContent($value, $rule, $data)
|
||||
{
|
||||
// 如果是群消息推送类型
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
// 如果targetType=1且groupPushSubType=2(群公告),则announcementContent必填
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
$groupPushSubType = isset($data['groupPushSubType']) ? intval($data['groupPushSubType']) : 1; // 默认1
|
||||
if ($targetType == 1 && $groupPushSubType == 2) {
|
||||
// 检查值是否存在且有效
|
||||
if (!isset($value) || $value === null || trim($value) === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证AI智能话术改写(当enableAiRewrite=1时,aiRewritePrompt必填)
|
||||
*/
|
||||
protected function checkEnableAiRewrite($value, $rule, $data)
|
||||
{
|
||||
// 如果是群消息推送类型且是群公告
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
$groupPushSubType = isset($data['groupPushSubType']) ? intval($data['groupPushSubType']) : 1; // 默认1
|
||||
if ($targetType == 1 && $groupPushSubType == 2) {
|
||||
// 检查值是否存在且有效
|
||||
if (!isset($value) || !in_array(intval($value), [0, 1])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证AI改写提示词(当enableAiRewrite=1时,aiRewritePrompt必填)
|
||||
*/
|
||||
protected function checkAiRewritePrompt($value, $rule, $data)
|
||||
{
|
||||
// 如果是群消息推送类型且是群公告
|
||||
if (isset($data['type']) && $data['type'] == self::TYPE_GROUP_PUSH) {
|
||||
$targetType = isset($data['targetType']) ? intval($data['targetType']) : 1; // 默认1
|
||||
$groupPushSubType = isset($data['groupPushSubType']) ? intval($data['groupPushSubType']) : 1; // 默认1
|
||||
$enableAiRewrite = isset($data['enableAiRewrite']) ? intval($data['enableAiRewrite']) : 0; // 默认0
|
||||
if ($targetType == 1 && $groupPushSubType == 2 && $enableAiRewrite == 1) {
|
||||
// 如果启用AI改写,提示词必填
|
||||
if (!isset($value) || $value === null || trim($value) === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ class CallRecordingListJob
|
||||
'secondMin' => 0,
|
||||
'secondMax' => 99999,
|
||||
'departmentIds' => '',
|
||||
'from' => date('Y-m-d') . ' 00:00:00',
|
||||
'to' => date('Y-m-d') . ' 00:00:00',
|
||||
'from' => date('Y-m-d 00:00:00', strtotime('-100 days')),
|
||||
'to' => date('Y-m-d 23:59:59'),
|
||||
'departmentId' => ''
|
||||
];
|
||||
|
||||
|
||||
@@ -58,11 +58,18 @@ class WorkbenchGroupPushJob
|
||||
{
|
||||
try {
|
||||
// 获取所有工作台
|
||||
$workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 256])->order('id desc')->select();
|
||||
$workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 264])->order('id desc')->select();
|
||||
foreach ($workbenches as $workbench) {
|
||||
// 获取工作台配置
|
||||
$config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find();
|
||||
if (!$config) {
|
||||
$configModel = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find();
|
||||
if (!$configModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 标准化配置
|
||||
$config = $this->normalizeConfig($configModel->toArray());
|
||||
if ($config === false) {
|
||||
Log::warning("消息群发:配置无效,工作台ID: {$workbench->id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,7 +79,16 @@ class WorkbenchGroupPushJob
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取内容库
|
||||
$targetType = intval($config['targetType']);
|
||||
$groupPushSubType = intval($config['groupPushSubType']);
|
||||
|
||||
// 如果是群推送且是群公告,暂时跳过(晚点处理)
|
||||
if ($targetType == 1 && $groupPushSubType == 2) {
|
||||
Log::info("群公告功能暂未实现,工作台ID: {$workbench->id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取内容库(群群发需要内容库,好友推送也需要内容库)
|
||||
$contentLibrary = $this->getContentLibrary($workbench, $config);
|
||||
if (empty($contentLibrary)) {
|
||||
continue;
|
||||
@@ -93,7 +109,7 @@ class WorkbenchGroupPushJob
|
||||
// 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件)
|
||||
// 当前,type 为文本、图片、动图表情包的时候,content为string, 其他情况为对象 {type: 'file/link/...', url: '', title: '', thunmbPath: '', desc: ''}
|
||||
|
||||
$targetType = isset($config['targetType']) ? intval($config['targetType']) : 1; // 默认1=群推送
|
||||
$targetType = intval($config['targetType']); // 默认1=群推送
|
||||
|
||||
$toAccountId = '';
|
||||
$username = Env::get('api.username', '');
|
||||
@@ -103,47 +119,56 @@ class WorkbenchGroupPushJob
|
||||
}
|
||||
// 建立WebSocket
|
||||
$wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
||||
|
||||
$ownerWechatIds = $config['ownerWechatIds'] ?? $this->getOwnerWechatIds($config);
|
||||
if ($targetType == 1) {
|
||||
// 群推送
|
||||
$this->sendToGroups($workbench, $config, $msgConf, $wsController);
|
||||
$this->sendToGroups($workbench, $config, $msgConf, $wsController, $ownerWechatIds);
|
||||
} else {
|
||||
// 好友推送
|
||||
$this->sendToFriends($workbench, $config, $msgConf, $wsController);
|
||||
$this->sendToFriends($workbench, $config, $msgConf, $wsController, $ownerWechatIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息
|
||||
*/
|
||||
protected function sendToGroups($workbench, $config, $msgConf, $wsController)
|
||||
protected function sendToGroups($workbench, $config, $msgConf, $wsController, array $ownerWechatIds = [])
|
||||
{
|
||||
$groups = json_decode($config['groups'], true);
|
||||
if (empty($groups)) {
|
||||
// 获取群推送子类型:1=群群发,2=群公告
|
||||
$groupPushSubType = intval($config['groupPushSubType'] ?? 1); // 默认1=群群发
|
||||
|
||||
// 如果是群公告,暂时跳过(晚点处理)
|
||||
if ($groupPushSubType == 2) {
|
||||
Log::info("群公告功能暂未实现,工作台ID: {$workbench['id']}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupsData = Db::name('wechat_group')->whereIn('id', $groups)->field('id,wechatAccountId,chatroomId,companyId,ownerWechatId')->select();
|
||||
// 群群发:从groups字段获取群ID列表
|
||||
$groups = $config['groups'] ?? [];
|
||||
if (empty($groups)) {
|
||||
Log::warning("群群发:未选择微信群,工作台ID: {$workbench['id']}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = Db::name('wechat_group')
|
||||
->whereIn('id', $groups);
|
||||
|
||||
if (!empty($ownerWechatIds)) {
|
||||
$query->whereIn('wechatAccountId', $ownerWechatIds);
|
||||
}
|
||||
|
||||
$groupsData = $query
|
||||
->field('id,wechatAccountId,chatroomId,companyId,ownerWechatId')
|
||||
->select();
|
||||
if (empty($groupsData)) {
|
||||
Log::warning("群群发:未找到微信群数据,工作台ID: {$workbench['id']}");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($msgConf as $content) {
|
||||
$sendData = [];
|
||||
$sqlData = [];
|
||||
|
||||
foreach ($groupsData as $group) {
|
||||
// msgType(1:文本 3:图片 43:视频 47:动图表情包(gif、其他表情包) 49:小程序/其他:图文、文件)
|
||||
$sqlData[] = [
|
||||
'workbenchId' => $workbench['id'],
|
||||
'contentId' => $content['id'],
|
||||
'groupId' => $group['id'],
|
||||
'friendId' => null,
|
||||
'targetType' => 1,
|
||||
'wechatAccountId' => $group['wechatAccountId'],
|
||||
'createTime' => time()
|
||||
];
|
||||
|
||||
// 构建发送数据
|
||||
$sendData = $this->buildSendData($content, $config, $group['wechatAccountId'], $group['id'], 'group');
|
||||
if (empty($sendData)) {
|
||||
@@ -154,77 +179,71 @@ class WorkbenchGroupPushJob
|
||||
foreach ($sendData as $send) {
|
||||
$wsController->sendCommunity($send);
|
||||
}
|
||||
//插入发送记录
|
||||
|
||||
// 准备插入发送记录
|
||||
$sqlData[] = [
|
||||
'workbenchId' => $workbench['id'],
|
||||
'contentId' => $content['id'],
|
||||
'groupId' => $group['id'],
|
||||
'friendId' => null,
|
||||
'targetType' => 1,
|
||||
'wechatAccountId' => $group['wechatAccountId'],
|
||||
'createTime' => time()
|
||||
];
|
||||
}
|
||||
|
||||
// 批量插入发送记录
|
||||
if (!empty($sqlData)) {
|
||||
Db::name('workbench_group_push_item')->insertAll($sqlData);
|
||||
Log::info("群群发:推送了" . count($sqlData) . "个群,工作台ID: {$workbench['id']}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友消息
|
||||
*/
|
||||
protected function sendToFriends($workbench, $config, $msgConf, $wsController)
|
||||
protected function sendToFriends($workbench, $config, $msgConf, $wsController, array $ownerWechatIds = [])
|
||||
{
|
||||
$friends = json_decode($config['friends'], true);
|
||||
$devices = json_decode($config['devices'] ?? '[]', true);
|
||||
$friends = $config['friends'] ?? [];
|
||||
$trafficPools = $config['trafficPools'] ?? [];
|
||||
$devices = $config['devices'] ?? [];
|
||||
|
||||
// 如果好友列表为空,则根据设备查询所有好友
|
||||
if (empty($friends)) {
|
||||
if (empty($devices)) {
|
||||
// 如果没有选择设备,则无法推送
|
||||
Log::warning('好友推送:未选择设备,无法推送全部好友');
|
||||
return false;
|
||||
}
|
||||
$friendsData = [];
|
||||
|
||||
// 根据设备查询所有好友
|
||||
$friendsData = Db::table('s2_company_account')
|
||||
->alias('ca')
|
||||
->join(['s2_wechat_account' => 'wa'], 'ca.id = wa.deviceAccountId')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatAccountId = wa.id')
|
||||
->where([
|
||||
'ca.status' => 0,
|
||||
'wf.isDeleted' => 0,
|
||||
'wa.deviceAlive' => 1,
|
||||
'wa.wechatAlive' => 1
|
||||
])
|
||||
->whereIn('wa.currentDeviceId', $devices)
|
||||
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.ownerWechatId')
|
||||
->group('wf.id')
|
||||
->select();
|
||||
} else {
|
||||
// 查询指定的好友信息
|
||||
$friendsData = Db::table('s2_wechat_friend')
|
||||
->whereIn('id', $friends)
|
||||
->where('isDeleted', 0)
|
||||
->field('id,wechatAccountId,wechatId,ownerWechatId')
|
||||
->select();
|
||||
// 指定好友
|
||||
if (!empty($friends)) {
|
||||
$friendsData = array_merge($friendsData, $this->getFriendsByIds($friends, $ownerWechatIds));
|
||||
}
|
||||
|
||||
// 流量池好友
|
||||
if (!empty($trafficPools)) {
|
||||
$friendsData = array_merge($friendsData, $this->getFriendsByTrafficPools($trafficPools, $workbench, $ownerWechatIds));
|
||||
}
|
||||
|
||||
// 如果未选择好友或流量池,则根据设备查询所有好友
|
||||
if (empty($friendsData)) {
|
||||
if (empty($devices)) {
|
||||
Log::warning('好友推送:未选择好友或流量池,且未选择设备,无法推送');
|
||||
return false;
|
||||
}
|
||||
$friendsData = $this->getFriendsByDevices($devices, $ownerWechatIds);
|
||||
}
|
||||
$friendsData = $this->deduplicateFriends($friendsData);
|
||||
if (empty($friendsData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取所有已推送的好友ID列表(去重,不限制时间范围,用于过滤避免重复推送)
|
||||
// 获取已推送的好友ID列表(不限制时间范围,避免重复推送)
|
||||
$sentFriendIds = Db::name('workbench_group_push_item')
|
||||
->where('workbenchId', $workbench->id)
|
||||
->where('targetType', 2)
|
||||
->column('friendId');
|
||||
$sentFriendIds = array_filter($sentFriendIds); // 过滤null值
|
||||
$sentFriendIds = array_unique($sentFriendIds); // 去重
|
||||
$sentFriendIds = array_unique(array_filter($sentFriendIds));
|
||||
|
||||
// 获取今日已推送的好友ID列表(用于计算今日推送人数)
|
||||
$today = date('Y-m-d');
|
||||
$todayStartTimestamp = strtotime($today . ' 00:00:00');
|
||||
$todayEndTimestamp = strtotime($today . ' 23:59:59');
|
||||
$todaySentFriendIds = Db::name('workbench_group_push_item')
|
||||
->where('workbenchId', $workbench->id)
|
||||
->where('targetType', 2)
|
||||
->whereTime('createTime', 'between', [$todayStartTimestamp, $todayEndTimestamp])
|
||||
->column('friendId');
|
||||
$todaySentFriendIds = array_filter($todaySentFriendIds); // 过滤null值
|
||||
$todaySentFriendIds = array_unique($todaySentFriendIds); // 去重
|
||||
|
||||
// 过滤掉所有已推送的好友(不限制时间范围,避免重复推送)
|
||||
// 过滤掉所有已推送的好友
|
||||
$friendsData = array_filter($friendsData, function($friend) use ($sentFriendIds) {
|
||||
return !in_array($friend['id'], $sentFriendIds);
|
||||
});
|
||||
@@ -237,13 +256,13 @@ class WorkbenchGroupPushJob
|
||||
// 重新索引数组
|
||||
$friendsData = array_values($friendsData);
|
||||
|
||||
// 计算剩余可推送人数(基于今日推送人数)
|
||||
$todaySentCount = count($todaySentFriendIds);
|
||||
// 计算剩余可推送人数(基于累计推送人数)
|
||||
$sentFriendCount = count($sentFriendIds);
|
||||
$maxPerDay = intval($config['maxPerDay']);
|
||||
$remainingCount = $maxPerDay - $todaySentCount;
|
||||
$remainingCount = $maxPerDay - $sentFriendCount;
|
||||
|
||||
if ($remainingCount <= 0) {
|
||||
Log::info('好友推送:今日推送人数已达上限');
|
||||
Log::info('好友推送:累计推送人数已达上限');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -416,6 +435,349 @@ class WorkbenchGroupPushJob
|
||||
return $sendData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据好友ID获取好友信息
|
||||
* @param array $friendIds
|
||||
* @return array
|
||||
*/
|
||||
protected function getFriendsByIds(array $friendIds, array $ownerWechatIds = [])
|
||||
{
|
||||
if (empty($friendIds)) {
|
||||
return [];
|
||||
}
|
||||
$query = Db::table('s2_wechat_friend')
|
||||
->whereIn('id', $friendIds)
|
||||
->where('isDeleted', 0);
|
||||
|
||||
if (!empty($ownerWechatIds)) {
|
||||
$query->whereIn('wechatAccountId', $ownerWechatIds);
|
||||
}
|
||||
|
||||
$friends = $query
|
||||
->field('id,wechatAccountId,wechatId,ownerWechatId')
|
||||
->select();
|
||||
if ($friends === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $friends;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备获取好友信息
|
||||
* @param array $deviceIds
|
||||
* @return array
|
||||
*/
|
||||
protected function getFriendsByDevices(array $deviceIds, array $ownerWechatIds = [])
|
||||
{
|
||||
if (empty($deviceIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = Db::table('s2_company_account')
|
||||
->alias('ca')
|
||||
->join(['s2_wechat_account' => 'wa'], 'ca.id = wa.deviceAccountId')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatAccountId = wa.id')
|
||||
->where([
|
||||
'ca.status' => 0,
|
||||
'wf.isDeleted' => 0,
|
||||
'wa.deviceAlive' => 1,
|
||||
'wa.wechatAlive' => 1
|
||||
])
|
||||
->whereIn('wa.currentDeviceId', $deviceIds);
|
||||
|
||||
if (!empty($ownerWechatIds)) {
|
||||
$query->whereIn('wf.wechatAccountId', $ownerWechatIds);
|
||||
}
|
||||
|
||||
$friends = $query
|
||||
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.ownerWechatId')
|
||||
->group('wf.id')
|
||||
->select();
|
||||
|
||||
if ($friends === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $friends->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据流量池获取好友信息
|
||||
* @param array $trafficPools
|
||||
* @param Workbench $workbench
|
||||
* @return array
|
||||
*/
|
||||
protected function getFriendsByTrafficPools(array $trafficPools, $workbench, array $ownerWechatIds = [])
|
||||
{
|
||||
if (empty($trafficPools)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$companyId = $workbench->companyId ?? 0;
|
||||
|
||||
$query = Db::name('traffic_source_package_item')
|
||||
->alias('tspi')
|
||||
->leftJoin('traffic_source_package tsp', 'tsp.id = tspi.packageId')
|
||||
->leftJoin('traffic_pool tp', 'tp.identifier = tspi.identifier')
|
||||
->leftJoin(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId')
|
||||
->leftJoin(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId')
|
||||
->whereIn('tspi.packageId', $trafficPools)
|
||||
->where('tsp.isDel', 0)
|
||||
->where('wf.isDeleted', 0)
|
||||
->whereNotNull('wf.id')
|
||||
->whereNotNull('wf.wechatAccountId')
|
||||
->where(function ($query) use ($companyId) {
|
||||
$query->whereIn('tsp.companyId', [$companyId, 0]);
|
||||
})
|
||||
->where(function ($query) use ($companyId) {
|
||||
$query->whereIn('tspi.companyId', [$companyId, 0]);
|
||||
});
|
||||
|
||||
if (!empty($ownerWechatIds)) {
|
||||
$query->whereIn('wf.wechatAccountId', $ownerWechatIds);
|
||||
}
|
||||
|
||||
$friends = $query
|
||||
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.ownerWechatId')
|
||||
->group('wf.id')
|
||||
->select();
|
||||
|
||||
if (empty($friends)) {
|
||||
Log::info('好友推送:流量池未匹配到好友');
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($friends === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $friends;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化群推送配置
|
||||
* @param array $config
|
||||
* @return array|false
|
||||
*/
|
||||
protected function normalizeConfig(array $config)
|
||||
{
|
||||
$config['targetType'] = intval($config['targetType'] ?? 1);
|
||||
$config['groupPushSubType'] = intval($config['groupPushSubType'] ?? 1);
|
||||
if (!in_array($config['groupPushSubType'], [1, 2], true)) {
|
||||
$config['groupPushSubType'] = 1;
|
||||
}
|
||||
|
||||
$config['pushType'] = !empty($config['pushType']) ? 1 : 0;
|
||||
$config['status'] = !empty($config['status']) ? 1 : 0;
|
||||
$config['isLoop'] = !empty($config['isLoop']) ? 1 : 0;
|
||||
|
||||
$config['startTime'] = $this->normalizeTimeString($config['startTime'] ?? '00:00');
|
||||
$config['endTime'] = $this->normalizeTimeString($config['endTime'] ?? '23:59');
|
||||
$config['maxPerDay'] = max(0, intval($config['maxPerDay'] ?? 0));
|
||||
|
||||
$config['friendIntervalMin'] = max(0, intval($config['friendIntervalMin'] ?? 0));
|
||||
$config['friendIntervalMax'] = max(0, intval($config['friendIntervalMax'] ?? $config['friendIntervalMin']));
|
||||
if ($config['friendIntervalMin'] > $config['friendIntervalMax']) {
|
||||
$config['friendIntervalMax'] = $config['friendIntervalMin'];
|
||||
}
|
||||
|
||||
$config['messageIntervalMin'] = max(0, intval($config['messageIntervalMin'] ?? 0));
|
||||
$config['messageIntervalMax'] = max(0, intval($config['messageIntervalMax'] ?? $config['messageIntervalMin']));
|
||||
if ($config['messageIntervalMin'] > $config['messageIntervalMax']) {
|
||||
$config['messageIntervalMax'] = $config['messageIntervalMin'];
|
||||
}
|
||||
|
||||
$config['ownerWechatIds'] = $this->deduplicateIds($this->jsonToArray($config['ownerWechatIds'] ?? []));
|
||||
$config['groups'] = $this->deduplicateIds($this->jsonToArray($config['groups'] ?? []));
|
||||
$config['friends'] = $this->deduplicateIds($this->jsonToArray($config['friends'] ?? []));
|
||||
$config['trafficPools'] = $this->deduplicateIds($this->jsonToArray($config['trafficPools'] ?? []));
|
||||
$config['devices'] = $this->deduplicateIds($this->jsonToArray($config['devices'] ?? []));
|
||||
$config['contentLibraries'] = $this->deduplicateIds($this->jsonToArray($config['contentLibraries'] ?? []));
|
||||
$config['postPushTags'] = $this->deduplicateIds($this->jsonToArray($config['postPushTags'] ?? []));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将混合类型转换为数组
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function jsonToArray($value): array
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化时间字符串,保留到分钟
|
||||
* @param string $time
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeTimeString(string $time): string
|
||||
{
|
||||
if (empty($time)) {
|
||||
return '00:00';
|
||||
}
|
||||
$parts = explode(':', $time);
|
||||
$hour = str_pad(intval($parts[0] ?? 0), 2, '0', STR_PAD_LEFT);
|
||||
$minute = str_pad(intval($parts[1] ?? 0), 2, '0', STR_PAD_LEFT);
|
||||
return "{$hour}:{$minute}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 对ID数组进行去重并清理无效值
|
||||
* @param array $ids
|
||||
* @return array
|
||||
*/
|
||||
protected function deduplicateIds(array $ids)
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$normalized = array_map(function ($value) {
|
||||
if (is_array($value) && isset($value['id'])) {
|
||||
return $value['id'];
|
||||
}
|
||||
if (is_object($value) && isset($value->id)) {
|
||||
return $value->id;
|
||||
}
|
||||
return $value;
|
||||
}, $ids);
|
||||
|
||||
$filtered = array_filter($normalized, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
if (empty($filtered)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique($filtered));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对内容列表根据内容ID去重
|
||||
* @param mixed $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function deduplicateContentList($contents)
|
||||
{
|
||||
if (empty($contents)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($contents instanceof \think\Collection || $contents instanceof \think\model\Collection) {
|
||||
$contents = $contents->toArray();
|
||||
} elseif ($contents instanceof \think\Model) {
|
||||
$contents = [$contents->toArray()];
|
||||
}
|
||||
|
||||
if (!is_array($contents)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$unique = [];
|
||||
|
||||
foreach ($contents as $content) {
|
||||
if ($content instanceof \think\Model) {
|
||||
$content = $content->toArray();
|
||||
} elseif (is_object($content)) {
|
||||
$content = (array)$content;
|
||||
}
|
||||
|
||||
if (!is_array($content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contentId = $content['id'] ?? null;
|
||||
if (empty($contentId) || isset($unique[$contentId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unique[$contentId] = true;
|
||||
$result[] = $content;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对好友数据进行去重
|
||||
* @param array $friends
|
||||
* @return array
|
||||
*/
|
||||
protected function deduplicateFriends(array $friends)
|
||||
{
|
||||
if (empty($friends)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$unique = [];
|
||||
$result = [];
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
if (empty($friend['id'])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($unique[$friend['id']])) {
|
||||
continue;
|
||||
}
|
||||
$unique[$friend['id']] = true;
|
||||
$result[] = $friend;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置中的客服微信ID列表
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected function getOwnerWechatIds($config)
|
||||
{
|
||||
if (empty($config['ownerWechatIds'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ownerWechatIds = $config['ownerWechatIds'];
|
||||
|
||||
if (is_string($ownerWechatIds)) {
|
||||
$decoded = json_decode($ownerWechatIds, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$ownerWechatIds = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_array($ownerWechatIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ownerWechatIds = array_map(function ($id) {
|
||||
return is_numeric($id) ? intval($id) : $id;
|
||||
}, $ownerWechatIds);
|
||||
|
||||
return $this->deduplicateIds($ownerWechatIds);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录发送历史
|
||||
@@ -441,10 +803,10 @@ class WorkbenchGroupPushJob
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
* 判断是否推送
|
||||
* @param Workbench $workbench 工作台
|
||||
* @param WorkbenchGroupPush $config 配置
|
||||
* @return array|bool
|
||||
* @param array $config 配置
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPush($workbench, $config)
|
||||
{
|
||||
@@ -463,27 +825,34 @@ class WorkbenchGroupPushJob
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetType = isset($config['targetType']) ? intval($config['targetType']) : 1; // 默认1=群推送
|
||||
$targetType = intval($config['targetType']); // 默认1=群推送
|
||||
|
||||
if ($targetType == 2) {
|
||||
// 好友推送:maxPerDay表示每日推送人数
|
||||
// 查询今日已推送的好友ID列表(去重,仅统计今日)
|
||||
// 查询已推送的好友ID列表(去重)
|
||||
$sentFriendIds = Db::name('workbench_group_push_item')
|
||||
->where('workbenchId', $workbench->id)
|
||||
->where('targetType', 2)
|
||||
->whereTime('createTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->column('friendId');
|
||||
$sentFriendIds = array_filter($sentFriendIds); // 过滤null值
|
||||
$count = count(array_unique($sentFriendIds)); // 去重后统计今日推送人数
|
||||
$count = count(array_unique($sentFriendIds)); // 去重后统计累计推送人数
|
||||
|
||||
if ($count >= $config['maxPerDay']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算本次同步的最早允许时间(按人数计算间隔)
|
||||
$interval = floor($totalSeconds / $config['maxPerDay']);
|
||||
$nextSyncTime = $startTimestamp + $count * $interval;
|
||||
if (time() < $nextSyncTime) {
|
||||
// 计算本次同步的最早允许时间(基于好友/消息间隔配置)
|
||||
$friendIntervalMin = max(0, intval($config['friendIntervalMin'] ?? 0));
|
||||
$messageIntervalMin = max(0, intval($config['messageIntervalMin'] ?? 0));
|
||||
$minInterval = max(1, $friendIntervalMin + $messageIntervalMin);
|
||||
|
||||
$lastSendTime = Db::name('workbench_group_push_item')
|
||||
->where('workbenchId', $workbench->id)
|
||||
->where('targetType', 2)
|
||||
->order('id', 'desc')
|
||||
->value('createTime');
|
||||
|
||||
if (!empty($lastSendTime) && (time() - $lastSendTime) < $minInterval) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -513,17 +882,24 @@ class WorkbenchGroupPushJob
|
||||
/**
|
||||
* 获取内容库
|
||||
* @param Workbench $workbench 工作台
|
||||
* @param WorkbenchGroupPush $config 配置
|
||||
* @param array $config 配置
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function getContentLibrary($workbench, $config)
|
||||
{
|
||||
$contentids = json_decode($config['contentLibraries'], true);
|
||||
if (empty($contentids)) {
|
||||
$targetType = intval($config['targetType']); // 默认1=群推送
|
||||
$groupPushSubType = intval($config['groupPushSubType']); // 默认1=群群发
|
||||
|
||||
// 如果是群公告,不需要内容库(晚点处理)
|
||||
if ($targetType == 1 && $groupPushSubType == 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetType = isset($config['targetType']) ? intval($config['targetType']) : 1; // 默认1=群推送
|
||||
$contentids = $config['contentLibraries'] ?? [];
|
||||
if (empty($contentids)) {
|
||||
Log::warning("未选择内容库,工作台ID: {$workbench->id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($config['pushType'] == 1) {
|
||||
$limit = 10;
|
||||
@@ -563,10 +939,12 @@ class WorkbenchGroupPushJob
|
||||
if ($config['isLoop'] == 1) {
|
||||
// 可以循环发送(只有群推送时才能为1)
|
||||
// 1. 优先获取未发送的内容
|
||||
$unsentContent = $query->where('wgpi.id', 'null')
|
||||
->order($order)
|
||||
->limit(0, $limit)
|
||||
->select();
|
||||
$unsentContent = $this->deduplicateContentList(
|
||||
$query->where('wgpi.id', 'null')
|
||||
->order($order)
|
||||
->limit(0, $limit)
|
||||
->select()
|
||||
);
|
||||
if (!empty($unsentContent)) {
|
||||
return $unsentContent;
|
||||
}
|
||||
@@ -585,18 +963,32 @@ class WorkbenchGroupPushJob
|
||||
return [];
|
||||
}
|
||||
|
||||
$sentContent = $query2->where('wgpi.contentId', '<', $lastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select();
|
||||
$sentContent = $this->deduplicateContentList(
|
||||
$query2->where('wgpi.contentId', '<', $lastSendData['contentId'])
|
||||
->order('wgpi.id ASC')
|
||||
->group('wgpi.contentId')
|
||||
->limit(0, $limit)
|
||||
->select()
|
||||
);
|
||||
|
||||
if (empty($sentContent)) {
|
||||
$sentContent = $query3->where('wgpi.contentId', '=', $fastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select();
|
||||
$sentContent = $this->deduplicateContentList(
|
||||
$query3->where('wgpi.contentId', '=', $fastSendData['contentId'])
|
||||
->order('wgpi.id ASC')
|
||||
->group('wgpi.contentId')
|
||||
->limit(0, $limit)
|
||||
->select()
|
||||
);
|
||||
}
|
||||
return $sentContent;
|
||||
} else {
|
||||
// 不能循环发送,只获取未发送的内容(好友推送时isLoop=0)
|
||||
$list = $query->where('wgpi.id', 'null')
|
||||
->order($order)
|
||||
->limit(0, $limit)
|
||||
->select();
|
||||
$list = $this->deduplicateContentList(
|
||||
$query->where('wgpi.id', 'null')
|
||||
->order($order)
|
||||
->limit(0, $limit)
|
||||
->select()
|
||||
);
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +176,9 @@ class CreateCompanyController extends BaseController
|
||||
protected function createFuncUsers(array $params): void
|
||||
{
|
||||
$seedCols = [
|
||||
['account' => $params['account'] . '_01', 'username' => $params['username'] . '_子账号01', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER],
|
||||
['account' => $params['account'] . '_02', 'username' => $params['username'] . '_子账号02', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER],
|
||||
['account' => $params['account'] . '_03', 'username' => $params['username'] . '_子账号03', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER],
|
||||
['account' => $params['account'] . '_01', 'username' => $params['username'] . '_子账号01', 'status' => UsersModel::ADMIN_STP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::MASTER_USER],
|
||||
['account' => $params['account'] . '_02', 'username' => $params['username'] . '_子账号02', 'status' => UsersModel::ADMIN_STP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::MASTER_USER],
|
||||
['account' => $params['account'] . '_03', 'username' => $params['username'] . '_子账号03', 'status' => UsersModel::ADMIN_STP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::MASTER_USER],
|
||||
['account' => $params['account'] . '_offline', 'username' => $params['username'] . '_处理离线专用', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER],
|
||||
['account' => $params['account'] . '_delete', 'username' => $params['username'] . '_处理删除专用', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER],
|
||||
];
|
||||
@@ -257,20 +257,7 @@ class CreateCompanyController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置部门权限
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function setDepartmentPrivileges(array $params): void
|
||||
{
|
||||
|
||||
$params = ArrHelper::getValue('companyId=departmentId', $params);
|
||||
$accountController = new \app\api\controller\AccountController();
|
||||
$accountController->setPrivileges(['id' => $params['companyId']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新项目
|
||||
@@ -292,9 +279,6 @@ class CreateCompanyController extends BaseController
|
||||
$this->createFuncUsers($params);
|
||||
Db::commit();
|
||||
|
||||
// 设置部门权限 ?????
|
||||
$this->setDepartmentPrivileges($params);
|
||||
|
||||
return ResponseHelper::success();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
|
||||
BIN
Server/依赖.rar
Normal file
BIN
Server/依赖.rar
Normal file
Binary file not shown.
@@ -54,13 +54,37 @@
|
||||
<!-- #endif -->
|
||||
|
||||
<view v-if="show" class="side-menu-container">
|
||||
<view class="side-menu-mask" @tap="closeSideMenu"></view>
|
||||
<view class="side-menu-mask" @tap="closeSideMenu" @touchstart="closeSettingsDropdown"></view>
|
||||
<view class="side-menu">
|
||||
<view class="side-menu-header">
|
||||
<text class="side-menu-title">AI数智员工</text>
|
||||
<!-- <text class="close-icon" @tap="closeSideMenu">
|
||||
<u-icon name="close" color="#333" size="24"></u-icon>
|
||||
</text> -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="header-right">
|
||||
<view class="settings-btn" @tap="toggleSettingsDropdown">
|
||||
<view class="settings-btn-content">
|
||||
<u-icon name="setting" color="#333" size="24"></u-icon>
|
||||
<text v-if="!checkingUpdate && hasNewVersion" class="version-badge">新</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 设置下拉菜单 -->
|
||||
<view v-if="showSettingsDropdown" class="settings-dropdown">
|
||||
<view class="dropdown-item combined-item" @tap="handleCheckUpdate(false)">
|
||||
<view class="icon-container">
|
||||
<u-icon name="reload" color="#5096ff" size="24"></u-icon>
|
||||
</view>
|
||||
<view class="text-container">
|
||||
<view class="text-top">
|
||||
<text class="main-text">检查更新</text>
|
||||
<text v-if="!checkingUpdate && hasNewVersion" class="update-badge">新</text>
|
||||
</view>
|
||||
<view class="text-bottom">
|
||||
<text class="version-info">当前版本 {{ currentVersion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
|
||||
@@ -170,21 +194,6 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="module-item" @tap="() => handleCheckUpdate(false)">
|
||||
<view class="module-left">
|
||||
<view class="module-icon green">
|
||||
<text class="iconfont icon-shezhi" style="color: #33cc99; font-size: 24px;"></text>
|
||||
</view>
|
||||
<view class="module-info">
|
||||
<text class="module-name">检查更新</text>
|
||||
<text class="module-desc" v-if="!checkingUpdate && !hasNewVersion">当前版本 {{ currentVersion }}</text>
|
||||
<text class="module-desc" v-if="checkingUpdate" style="color: #33cc99;">检查中...</text>
|
||||
<text class="module-desc" v-if="!checkingUpdate && hasNewVersion" style="color: #ff6699;">发现新版本 {{ latestVersion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="module-item" @tap="showSettings" v-if='hide'>
|
||||
<view class="module-left">
|
||||
@@ -262,6 +271,7 @@
|
||||
data() {
|
||||
return {
|
||||
hide : false,
|
||||
showSettingsDropdown: false, // 控制设置下拉菜单显示
|
||||
functionStatus: {
|
||||
'autoLike': false,
|
||||
'momentsSync': false,
|
||||
@@ -374,6 +384,18 @@
|
||||
},
|
||||
closeSideMenu() {
|
||||
this.$emit('close');
|
||||
// 关闭设置下拉菜单
|
||||
this.showSettingsDropdown = false;
|
||||
},
|
||||
|
||||
// 切换设置下拉菜单
|
||||
toggleSettingsDropdown() {
|
||||
this.showSettingsDropdown = !this.showSettingsDropdown;
|
||||
},
|
||||
|
||||
// 点击页面其他区域关闭下拉菜单
|
||||
closeSettingsDropdown() {
|
||||
this.showSettingsDropdown = false;
|
||||
},
|
||||
// 获取功能开关状态
|
||||
async getFunctionStatus() {
|
||||
@@ -947,6 +969,7 @@
|
||||
align-items: center;
|
||||
padding: 20px 15px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.side-menu-title {
|
||||
@@ -960,6 +983,119 @@
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-btn-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
background-color: #ff6699;
|
||||
border-radius: 8px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(50%, -30%);
|
||||
}
|
||||
|
||||
.settings-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
width: 160px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.combined-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.text-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
|
||||
.main-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.dropdown-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.update-badge {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
background-color: #ff6699;
|
||||
border-radius: 10px;
|
||||
padding: 2px 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.function-module {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name" : "AI数智员工",
|
||||
"appid" : "__UNI__9421F6C",
|
||||
"description" : "",
|
||||
"versionName" : "1.1.1",
|
||||
"versionName" : "1.1.2",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
|
||||
Reference in New Issue
Block a user