diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts index 8c14f3e9..0165179a 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts @@ -55,6 +55,20 @@ export function transferWechatFriends(params: { return request("/v1/wechats/transfer-friends", params, "POST"); } +// 获取客服账号列表 +export function getKefuAccountsList() { + return request("/v1/kefu/accounts/list", {}, "GET"); +} + +// 转移好友到客服账号 +export function transferFriend(params: { + friendId: string; + toAccountId: string; + comment?: string; +}) { + return request("/v1/friend/transfer", params, "POST"); +} + // 导出朋友圈接口(直接下载文件) export async function exportWechatMoments(params: { wechatId: string; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts index 9245ba9f..6a374dba 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts @@ -102,9 +102,12 @@ export interface WechatAccountSummary { export interface Friend { id: string; + friendId?: string; avatar: string; nickname: string; wechatId: string; + accountUserName: string; + accountRealName: string; remark: string; addTime: string; lastInteraction: string; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss index 95c33d8a..41530573 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss @@ -684,99 +684,114 @@ .friend-card { display: flex; - align-items: center; - padding: 14px; + align-items: flex-start; + padding: 16px; background: #fff; border: 1px solid #f0f0f0; border-radius: 12px; - margin-bottom: 10px; + margin-bottom: 12px; gap: 12px; - transition: box-shadow 0.2s, border-color 0.2s; + transition: all 0.2s; + cursor: pointer; - &:hover { - border-color: #cfe2ff; - box-shadow: 0 6px 16px rgba(24, 144, 255, 0.15); + &:active { + background: #f8f9fa; + border-color: #1677ff; + transform: scale(0.98); } } .friend-avatar { - width: 48px; - height: 48px; + flex-shrink: 0; + width: 52px; + height: 52px; .adm-avatar { - width: 48px; - height: 48px; - border-radius: 50%; + width: 52px; + height: 52px; + border-radius: 50%; + border: 2px solid #f0f0f0; } } .friend-main { flex: 1; min-width: 0; - } - - .friend-name-row { - display: flex; - align-items: center; + display: flex; + flex-direction: column; gap: 8px; - margin-bottom: 4px; } - .friend-name { - font-size: 15px; + .friend-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + + .friend-name { + font-size: 16px; font-weight: 600; color: #111; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .friend-value { flex-shrink: 0; + + .value-amount { + font-size: 15px; + font-weight: 600; + color: #fa541c; + white-space: nowrap; + } + } + + .friend-info { + display: flex; + flex-direction: column; + gap: 4px; + } + + .friend-info-item { + font-size: 13px; + color: #666; + display: flex; + align-items: center; + gap: 4px; + + .info-label { + color: #999; + flex-shrink: 0; + } + + .info-value { + color: #666; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .friend-tags { display: flex; flex-wrap: wrap; - gap: 4px; - } + gap: 6px; + margin-top: 4px; + } .friend-tag { font-size: 11px; - padding: 2px 8px; - border-radius: 999px; - background: #f5f5f5; - color: #666; - } - - .friend-id-row { - font-size: 12px; - color: #999; - margin-bottom: 6px; - } - - .friend-status-row { - display: flex; - flex-wrap: wrap; - gap: 6px; - } - - .friend-status-chip { + padding: 4px 10px; + border-radius: 12px; background: #f0f7ff; color: #1677ff; - font-size: 11px; - padding: 2px 8px; - border-radius: 8px; - } - - .friend-value { - text-align: right; - - .value-label { - font-size: 11px; - color: #999; - margin-bottom: 4px; - } - - .value-amount { - font-size: 14px; - font-weight: 600; - color: #fa541c; - } + font-weight: 500; + white-space: nowrap; } } @@ -851,11 +866,98 @@ margin-top: 20px; } - .popup-footer { - margin-top: 24px; - padding-top: 16px; - border-top: 1px solid #f0f0f0; - } + .popup-footer { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; + } + + .friend-info-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: #f5f5f5; + border-radius: 8px; + + .friend-info-text { + flex: 1; + + .friend-info-name { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 4px; + } + + .friend-info-id { + font-size: 12px; + color: #999; + } + } + } + + .loading-accounts, + .empty-accounts { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + color: #999; + font-size: 14px; + } + + .kefu-accounts-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 300px; + overflow-y: auto; + + .kefu-account-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px; + border: 1px solid #e0e0e0; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + background: white; + + &:hover { + border-color: #1677ff; + background: #f0f7ff; + } + + &.selected { + border-color: #1677ff; + background: #e6f4ff; + } + + .account-info { + flex: 1; + + .account-name { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 4px; + } + + .account-id { + font-size: 12px; + color: #999; + } + } + + .selected-icon { + color: #1677ff; + font-size: 18px; + font-weight: bold; + } + } + } .export-form { margin-top: 20px; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 06609940..a985f182 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -14,7 +14,7 @@ import { DatePicker, InfiniteScroll, } from "antd-mobile"; -import { Input } from "antd"; +import { Input, Select } from "antd"; import NavCommon from "@/components/NavCommon"; import { SearchOutlined, @@ -34,6 +34,8 @@ import { getWechatAccountOverview, getWechatMoments, exportWechatMoments, + getKefuAccountsList, + transferFriend, } from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; @@ -84,6 +86,15 @@ const WechatAccountDetail: React.FC = () => { const [showEndTimePicker, setShowEndTimePicker] = useState(false); const [exportLoading, setExportLoading] = useState(false); + // 迁移好友相关状态 + const [showTransferFriendPopup, setShowTransferFriendPopup] = useState(false); + const [selectedFriend, setSelectedFriend] = useState(null); + const [kefuAccounts, setKefuAccounts] = useState([]); + const [selectedKefuAccountId, setSelectedKefuAccountId] = useState(""); + const [transferComment, setTransferComment] = useState(""); + const [transferFriendLoading, setTransferFriendLoading] = useState(false); + const [loadingKefuAccounts, setLoadingKefuAccounts] = useState(false); + // 获取基础信息 const fetchAccountInfo = useCallback(async () => { if (!id) return; @@ -237,9 +248,12 @@ const WechatAccountDetail: React.FC = () => { return { id: friend.id.toString(), + friendId: friend.friendId || friend.id?.toString() || "", avatar: friend.avatar || "/placeholder.svg", nickname: friend.nickname || "未知用户", wechatId: friend.wechatId || "", + accountUserName: friend.accountUserName || "", + accountRealName: friend.accountRealName || "", remark: friend.notes || "", addTime: friend.createTime || new Date().toISOString().split("T")[0], @@ -466,7 +480,66 @@ const WechatAccountDetail: React.FC = () => { }; const handleFriendClick = (friend: Friend) => { - navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`); + setSelectedFriend(friend); + setShowTransferFriendPopup(true); + // 加载客服账号列表 + fetchKefuAccounts(); + }; + + // 获取客服账号列表 + const fetchKefuAccounts = useCallback(async () => { + setLoadingKefuAccounts(true); + try { + const response = await getKefuAccountsList(); + // 数据结构:{ code: 200, msg: "success", data: { total: 7, list: [...] } } + const accountsList = response?.data?.list || response?.list || (Array.isArray(response) ? response : []); + setKefuAccounts(accountsList); + } catch (error) { + console.error("获取客服账号列表失败:", error); + Toast.show({ + content: "获取客服账号列表失败", + position: "top", + }); + } finally { + setLoadingKefuAccounts(false); + } + }, []); + + // 确认转移好友 + const handleConfirmTransferFriend = async () => { + if (!selectedFriend) { + Toast.show({ content: "请选择好友", position: "top" }); + return; + } + if (!selectedKefuAccountId) { + Toast.show({ content: "请选择目标客服账号", position: "top" }); + return; + } + + setTransferFriendLoading(true); + try { + await transferFriend({ + friendId: selectedFriend.friendId || selectedFriend.id, + toAccountId: selectedKefuAccountId, + comment: transferComment || undefined, + }); + + Toast.show({ content: "转移成功", position: "top" }); + setShowTransferFriendPopup(false); + setSelectedFriend(null); + setSelectedKefuAccountId(""); + setTransferComment(""); + // 刷新好友列表 + fetchFriendsList(1, searchQuery, false); + } catch (error: any) { + console.error("转移好友失败:", error); + Toast.show({ + content: error?.message || "转移失败,请重试", + position: "top", + }); + } finally { + setTransferFriendLoading(false); + } }; const handleLoadMoreMoments = async () => { @@ -903,38 +976,53 @@ const WechatAccountDetail: React.FC = () => {
-
+
{friend.nickname || "未知好友"}
- +
+
+ {friend.valueFormatted + || (typeof friend.value === "number" + ? `¥${friend.value.toLocaleString()}` + : "¥3")} +
+
-
- ID: {friend.wechatId || "-"} -
-
- {friend.statusTags?.map((tag, idx) => ( - - {tag} - - ))} - {friend.remark && ( - - {friend.remark} +
+
+ 微信号: + + {friend.wechatId || "-"} +
+ {(friend.accountUserName || friend.accountRealName) && ( +
+ 归属: + + {friend.accountUserName || ""} + {friend.accountRealName && `(${friend.accountRealName})`} + +
)}
-
-
-
- {friend.valueFormatted - || (typeof friend.value === "number" - ? `¥${friend.value.toLocaleString()}` - : "估值 -")} -
+ {(friend.statusTags?.length > 0 || friend.remark) && ( +
+ {friend.statusTags?.map((tag, idx) => ( + + {tag} + + ))} + {friend.remark && ( + + {friend.remark} + + )} +
+ )}
))} @@ -1405,8 +1493,122 @@ const WechatAccountDetail: React.FC = () => {
- {/* 好友详情弹窗 */} - {/* Removed */} + {/* 迁移好友弹窗 */} + { + setShowTransferFriendPopup(false); + setSelectedFriend(null); + setSelectedKefuAccountId(""); + setTransferComment(""); + }} + bodyStyle={{ borderRadius: "16px 16px 0 0" }} + > +
+
+

迁移好友

+ +
+ +
+ {/* 好友信息 */} + {selectedFriend && ( +
+ +
+ +
+
+ {selectedFriend.nickname || "未知好友"} +
+
+ {selectedFriend.wechatId || "-"} +
+
+
+
+ )} + + {/* 选择客服账号 */} +
+ + {loadingKefuAccounts ? ( +
+ + 加载中... +
+ ) : kefuAccounts.length === 0 ? ( +
暂无客服账号
+ ) : ( + + )} +
+ + {/* 备注 */} +
+ + setTransferComment(e.target.value)} + allowClear + /> +
+
+ +
+ + +
+
+
); }; diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts index fddb19bc..c8042414 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts @@ -38,6 +38,7 @@ export function fetchTransferFriends(params: { limit?: number; keyword?: string; workbenchId: number; + isRecycle?: number; // 0=未回收, 1=已回收, undefined=全部 }) { return request("/v1/workbench/transfer-friends", params, "GET"); } diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/SendRcrodModal.tsx b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/SendRcrodModal.tsx index e9d82452..e0d5548b 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/SendRcrodModal.tsx +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/SendRcrodModal.tsx @@ -35,10 +35,11 @@ const SendRcrodModal: React.FC = ({ const [searchKeyword, setSearchKeyword] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [total, setTotal] = useState(0); + const [recycleFilter, setRecycleFilter] = useState(undefined); // undefined=全部, 0=未回收, 1=已回收 const pageSize = 20; // 获取分发记录数据 - const fetchSendRecords = async (page = 1, keyword = "") => { + const fetchSendRecords = async (page = 1, keyword = "", isRecycle?: number) => { if (!ruleId) return; setLoading(true); @@ -48,6 +49,7 @@ const SendRcrodModal: React.FC = ({ page, limit: pageSize, keyword, + isRecycle, }); console.log(detailRes); @@ -68,21 +70,29 @@ const SendRcrodModal: React.FC = ({ setCurrentPage(1); setSearchQuery(""); setSearchKeyword(""); - fetchSendRecords(1, ""); + setRecycleFilter(undefined); + fetchSendRecords(1, "", undefined); } }, [visible, ruleId]); // 搜索关键词变化时触发搜索 useEffect(() => { - if (!visible || !ruleId || searchKeyword === "") return; + if (!visible || !ruleId) return; setCurrentPage(1); - fetchSendRecords(1, searchKeyword); + fetchSendRecords(1, searchKeyword, recycleFilter); }, [searchKeyword]); + // 筛选条件变化时触发搜索 + useEffect(() => { + if (!visible || !ruleId) return; + setCurrentPage(1); + fetchSendRecords(1, searchKeyword, recycleFilter); + }, [recycleFilter]); + // 页码变化 useEffect(() => { if (!visible || !ruleId) return; - fetchSendRecords(currentPage, searchKeyword); + fetchSendRecords(currentPage, searchKeyword, recycleFilter); }, [currentPage]); // 处理页码变化 @@ -160,6 +170,37 @@ const SendRcrodModal: React.FC = ({ + {/* 回收状态筛选 */} +
+
回收状态:
+
+
setRecycleFilter(undefined)} + > + 全部 +
+
setRecycleFilter(0)} + > + 未回收 +
+
setRecycleFilter(1)} + > + 已回收 +
+
+
+ {/* 分发记录列表 */}
{loading ? ( diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss index 1415242c..91923e5d 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss @@ -251,6 +251,52 @@ font-weight: 500; } +.filterBar { + display: flex; + align-items: center; + padding: 12px 16px; + background: white; + border-bottom: 1px solid #f0f0f0; + + .filterLabel { + font-size: 14px; + font-weight: 500; + color: #333; + margin-right: 12px; + white-space: nowrap; + } + + .filterOptions { + display: flex; + gap: 8px; + flex: 1; + + .filterOption { + flex: 1; + padding: 8px 16px; + border: 1px solid #e0e0e0; + border-radius: 8px; + font-size: 14px; + color: #666; + cursor: pointer; + transition: all 0.2s; + background: white; + text-align: center; + + &:hover { + border-color: #1677ff; + color: #1677ff; + } + + &.active { + background: #1677ff; + border-color: #1677ff; + color: white; + } + } + } +} + .accountModalFooter { padding: 16px 20px; border-top: 1px solid #f0f0f0; diff --git a/Server/application/api/controller/AutomaticAssign.php b/Server/application/api/controller/AutomaticAssign.php index a9e24102..6a0953cf 100644 --- a/Server/application/api/controller/AutomaticAssign.php +++ b/Server/application/api/controller/AutomaticAssign.php @@ -174,7 +174,8 @@ class AutomaticAssign extends BaseController public function allotWechatFriend($data = [],$isInner = false,$errorNum = 0) { // 获取授权token - $authorization = trim($this->request->header('authorization', $this->authorization)); + $authorization = $this->authorization; + if (empty($authorization)) { if($isInner){ return json_encode(['code'=>500,'msg'=>'缺少授权信息']); @@ -209,8 +210,8 @@ class AutomaticAssign extends BaseController // 发送请求 $url = $this->baseUrl . 'api/WechatFriend/allot?wechatFriendId='.$wechatFriendId.'¬ifyReceiver='.$notifyReceiver.'&comment='.$comment.'&toAccountId='.$toAccountId.'&optFrom='.$optFrom; $result = requestCurl($url, [], 'PUT', $header, 'json'); - - if (empty($result)) { + $response = handleApiResponse($result); + if (empty($response)) { if($isInner){ return json_encode(['code'=>200,'msg'=>'微信好友分配成功']); }else{ diff --git a/Server/application/chukebao/controller/WechatChatroomController.php b/Server/application/chukebao/controller/WechatChatroomController.php index 9c33baed..5d521143 100644 --- a/Server/application/chukebao/controller/WechatChatroomController.php +++ b/Server/application/chukebao/controller/WechatChatroomController.php @@ -15,7 +15,8 @@ class WechatChatroomController extends BaseController $page = $this->request->param('page', 1); $limit = $this->request->param('limit', 10); $keyword = $this->request->param('keyword', ''); - $groupIds = $this->request->param('groupIds', ''); + $groupIds = $this->request->param('groupId', ''); + $ownerWechatId = $this->request->param('ownerWechatId', ''); $accountId = $this->getUserInfo('s2_accountId'); if (empty($accountId)){ return ResponseHelper::error('请先登录'); @@ -37,6 +38,10 @@ class WechatChatroomController extends BaseController $query->where('groupIds', $groupIds); } + if (!empty($ownerWechatId)) { + $query->where('ownerWechatId', $ownerWechatId); + } + $query->order('id desc'); $total = $query->count(); $list = $query->page($page, $limit)->select(); diff --git a/Server/application/chukebao/controller/WechatFriendController.php b/Server/application/chukebao/controller/WechatFriendController.php index da4782c2..aaf541f1 100644 --- a/Server/application/chukebao/controller/WechatFriendController.php +++ b/Server/application/chukebao/controller/WechatFriendController.php @@ -14,7 +14,8 @@ class WechatFriendController extends BaseController $page = $this->request->param('page', 1); $limit = $this->request->param('limit', 10); $keyword = $this->request->param('keyword', ''); - $groupIds = $this->request->param('groupIds', ''); + $groupIds = $this->request->param('groupId', ''); + $ownerWechatId = $this->request->param('ownerWechatId', ''); $accountId = $this->getUserInfo('s2_accountId'); if (empty($accountId)) { return ResponseHelper::error('请先登录'); @@ -38,6 +39,10 @@ class WechatFriendController extends BaseController $query->where('groupIds', $groupIds); } + if (!empty($ownerWechatId)) { + $query->where('ownerWechatId', $ownerWechatId); + } + $query->order('id desc'); $total = $query->count(); $list = $query->page($page, $limit)->select(); diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 041d596a..0dfa952b 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -136,6 +136,7 @@ Route::group('v1/', function () { // 好友相关 Route::group('friend', function () { Route::get('', 'app\cunkebao\controller\friend\GetFriendListV1Controller@index'); // 获取好友列表 + Route::post('transfer', 'app\cunkebao\controller\friend\GetFriendListV1Controller@transfer'); // 好友转移 }); //群相关 diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php index 969eaeed..d330b9f0 100644 --- a/Server/application/cunkebao/controller/WorkbenchController.php +++ b/Server/application/cunkebao/controller/WorkbenchController.php @@ -1884,6 +1884,7 @@ class WorkbenchController extends Controller $limit = $this->request->param('limit', 10); $keyword = $this->request->param('keyword', ''); $workbenchId = $this->request->param('workbenchId', ''); + $isRecycle = $this->request->param('isRecycle', ''); if (empty($workbenchId)) { return json(['code' => 400, 'msg' => '参数错误']); } @@ -1897,7 +1898,7 @@ class WorkbenchController extends Controller ->join(['s2_wechat_friend' => 'wf'], 'wtc.wechatFriendId = wf.id') ->join('users u', 'wtc.wechatAccountId = u.s2_accountId', 'left') ->field([ - 'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime', + 'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime','wtc.recycleTime', 'wf.wechatId', 'wf.alias', 'wf.nickname', 'wf.avatar', 'wf.gender', 'wf.phone', 'u.account', 'u.username' ]) @@ -1908,11 +1909,18 @@ class WorkbenchController extends Controller $query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username', 'like', '%' . $keyword . '%'); } + if ($isRecycle != '' || $isRecycle != null) { + $query->where('isRecycle',$isRecycle); + } + + + $total = $query->count(); $list = $query->page($page, $limit)->select(); foreach ($list as &$item) { $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']); + $item['recycleTime'] = date('Y-m-d H:i:s', $item['recycleTime']); } unset($item); diff --git a/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php b/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php index dc7a525f..4ba300f4 100644 --- a/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php +++ b/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php @@ -5,6 +5,7 @@ use app\common\model\Device as DeviceModel; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\WechatFriendShip as WechatFriendShipModel; use app\cunkebao\controller\BaseController; +use app\api\controller\AutomaticAssign; use think\Db; /** @@ -66,29 +67,45 @@ class GetFriendListV1Controller extends BaseController $where[] = ['ownerWechatId','in',$wechatIds]; $data = Db::table('s2_wechat_friend') - ->field(['nickname','avatar','alias','id','wechatId','ownerNickname','ownerAlias','ownerWechatId','createTime']) + ->field([ + 'id', 'nickname', 'avatar', 'alias', 'wechatId', + 'gender', 'phone', 'createTime', 'updateTime', 'deleteTime', + 'ownerNickname', 'ownerAlias', 'ownerWechatId', + 'accountUserName', 'accountNickname', 'accountRealName' + ]) ->where($where); $total = $data->count(); $list = $data->page($page, $limit)->order('id DESC')->select(); - -// $data = WechatFriendShipModel::alias('wf') -// ->field(['wa1.nickname','wa1.avatar','wa1.alias','wf.id','wf.wechatId','wa2.nickname as ownerNickname','wa2.alias as ownerAlias','wa2.wechatId as ownerWechatId','wf.createTime']) -// ->Join('wechat_account wa1','wf.wechatId = wa1.wechatId') -// ->Join('wechat_account wa2','wf.ownerWechatId = wa2.wechatId') -// ->where($where); -// -// $total = $data->count(); -// $list = $data->page($page, $limit)->order('wf.id DESC')->group('wf.id')->select(); - - - + // 格式化时间字段和处理数据 + $formattedList = []; + foreach ($list as $item) { + $formattedItem = [ + 'id' => $item['id'], + 'nickname' => $item['nickname'] ?? '', + 'avatar' => $item['avatar'] ?? '', + 'alias' => $item['alias'] ?? '', + 'wechatId' => $item['wechatId'] ?? '', + 'gender' => $item['gender'] ?? 0, + 'phone' => $item['phone'] ?? '', + 'account' => $item['accountUserName'] ?? '', + 'username' => $item['accountRealName'] ?? '', + 'createTime' => !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : '1970-01-01 08:00:00', + 'updateTime' => !empty($item['updateTime']) ? date('Y-m-d H:i:s', $item['updateTime']) : '1970-01-01 08:00:00', + 'deleteTime' => !empty($item['deleteTime']) ? date('Y-m-d H:i:s', $item['deleteTime']) : '1970-01-01 08:00:00', + 'ownerNickname' => $item['ownerNickname'] ?? '', + 'ownerAlias' => $item['ownerAlias'] ?? '', + 'ownerWechatId' => $item['ownerWechatId'] ?? '', + 'accountNickname' => $item['accountNickname'] ?? '' + ]; + $formattedList[] = $formattedItem; + } return json([ 'code' => 200, 'msg' => '获取成功', 'data' => [ - 'list' => $list, + 'list' => $formattedList, 'total' => $total, 'companyId' => $this->getUserInfo('companyId') ] @@ -100,4 +117,95 @@ class GetFriendListV1Controller extends BaseController ]); } } + + /** + * 好友转移 + * @return \think\response\Json + */ + public function transfer() + { + $friendId = $this->request->param('friendId', 0); + $toAccountId = $this->request->param('toAccountId', ''); + $comment = $this->request->param('comment', ''); + $companyId = $this->getUserInfo('companyId'); + + // 参数验证 + if (empty($friendId)) { + return json([ + 'code' => 400, + 'msg' => '好友ID不能为空' + ]); + } + + if (empty($toAccountId)) { + return json([ + 'code' => 400, + 'msg' => '目标账号ID不能为空' + ]); + } + + try { + // 验证目标账号是否存在且属于当前公司 + $accountInfo = Db::table('s2_company_account') + ->where('id', $toAccountId) + ->where('departmentId', $companyId) + ->field('id as accountId, userName as accountUserName, realName as accountRealName, nickname as accountNickname, tenantId') + ->find(); + + if (empty($accountInfo)) { + return json([ + 'code' => 404, + 'msg' => '目标账号不存在' + ]); + } + + + // 调用 AutomaticAssign 进行好友转移 + $automaticAssign = new AutomaticAssign(); + $result = $automaticAssign->allotWechatFriend([ + 'wechatFriendId' => $friendId, + 'toAccountId' => $toAccountId, + 'comment' => $comment, + 'notifyReceiver' => false, + 'optFrom' => 4 + ], true); + + $resultData = json_decode($result, true); + + if (!empty($resultData) && $resultData['code'] == 200) { + // 转移成功后更新数据库 + $updateData = [ + 'accountId' => $accountInfo['accountId'], + 'accountUserName' => $accountInfo['accountUserName'], + 'accountRealName' => $accountInfo['accountRealName'], + 'accountNickname' => $accountInfo['accountNickname'], + 'updateTime' => time() + ]; + + Db::table('s2_wechat_friend') + ->where('id', $friendId) + ->update($updateData); + + return json([ + 'code' => 200, + 'msg' => '好友转移成功', + 'data' => [ + 'friendId' => $friendId, + 'toAccountId' => $toAccountId + ] + ]); + } else { + return json([ + 'code' => 500, + 'msg' => '好友转移失败:' . ($resultData['msg'] ?? '未知错误') + ]); + } + + } catch (\Exception $e) { + return json([ + 'code' => 500, + 'msg' => '好友转移失败:' . $e->getMessage() + ]); + } + } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/wechat/GetWechatOnDeviceFriendsV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatOnDeviceFriendsV1Controller.php index 9a95e862..cd544c4d 100644 --- a/Server/application/cunkebao/controller/wechat/GetWechatOnDeviceFriendsV1Controller.php +++ b/Server/application/cunkebao/controller/wechat/GetWechatOnDeviceFriendsV1Controller.php @@ -43,10 +43,12 @@ class GetWechatOnDeviceFriendsV1Controller extends BaseController [ 'w.id', 'w.nickname', 'w.avatar', 'w.wechatId', 'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount', - 'f.memo', 'f.tags' + 'f.memo', 'f.tags', + 'ff.accountUserName', 'ff.accountRealName','ff.id AS friendId' ] ) - ->join('wechat_account w', 'w.wechatId = f.wechatId'); + ->join('wechat_account w', 'w.wechatId = f.wechatId') + ->join(['s2_wechat_friend' => 'ff'], 'ff.id = f.id'); foreach ($where as $key => $value) { if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') { diff --git a/Server/application/job/WorkbenchGroupCreateJob.php b/Server/application/job/WorkbenchGroupCreateJob.php index 6225a4e4..bfb77b15 100644 --- a/Server/application/job/WorkbenchGroupCreateJob.php +++ b/Server/application/job/WorkbenchGroupCreateJob.php @@ -77,7 +77,7 @@ class WorkbenchGroupCreateJob { try { // 1. 查询启用了建群功能的数据 - $workbenches = Workbench::where(['status' => 1, 'type' => 4, 'isDel' => 0])->order('id desc')->select(); + $workbenches = Workbench::where(['status' => 1, 'type' => 4, 'isDel' => 0,'id' => 315])->order('id desc')->select(); foreach ($workbenches as $workbench) { // 获取工作台配置 $config = WorkbenchGroupCreate::where('workbenchId', $workbench->id)->find(); @@ -120,7 +120,6 @@ class WorkbenchGroupCreateJob } } } - if (empty($groupMemberWechatId)) { continue; } @@ -150,7 +149,7 @@ class WorkbenchGroupCreateJob } // 计算随机群人数(不包含管理员,只减去群主成员数) $groupRandNum = mt_rand($config['groupSizeMin'], $config['groupSizeMax']) - count($groupMember); - + // 分批处理待入群用户 $addGroupUser = []; $totalRows = count($joinUser); @@ -168,7 +167,7 @@ class WorkbenchGroupCreateJob $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); } $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); - + // 遍历每批用户 foreach ($addGroupUser as $batchUsers) { $this->processBatchUsers($workbench, $config, $batchUsers, $groupMemberId, $groupMemberWechatId, $groupRandNum, $webSocket); @@ -201,7 +200,7 @@ class WorkbenchGroupCreateJob $groupOwnerWechatIds[] = $member['ownerWechatId']; } } - + // 如果从好友表获取不到,使用群主成员微信ID列表(作为备用) if (empty($groupOwnerWechatIds)) { @@ -225,19 +224,20 @@ class WorkbenchGroupCreateJob } } - exit_data($adminWechatIds); + // 3. 从流量池用户中筛选出是群主好友的用户(按微信账号分组) $ownerFriendIdsByAccount = []; $wechatIds = []; // 获取群主的好友关系(从流量池中筛选) - $ownerFriends = Db::name('wechat_friendship')->alias('f') - ->join(['s2_wechat_account' => 'a'], 'f.ownerWechatId=a.wechatId') - ->where('f.companyId', $workbench->companyId) + $ownerFriends = Db::table('s2_wechat_friend')->alias('f') + ->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id') ->whereIn('f.wechatId', $batchUsers) - ->whereIn('f.ownerWechatId', $groupOwnerWechatIds) + ->whereIn('a.wechatId', $groupOwnerWechatIds) + ->where('f.isDeleted', 0) ->field('f.id,f.wechatId,a.id as wechatAccountId') ->select(); + if (empty($ownerFriends)) { Log::warning("未找到群主的好友,跳过。工作台ID: {$workbench->id}"); return; @@ -252,13 +252,12 @@ class WorkbenchGroupCreateJob $ownerFriendIdsByAccount[$wechatAccountId][] = $friend['id']; $wechatIds[$friend['id']] = $friend['wechatId']; } - + // 4. 遍历每个微信账号,创建群 foreach ($ownerFriendIdsByAccount as $wechatAccountId => $ownerFriendIds) { // 4.1 获取当前账号的管理员好友ID $currentAdminFriendIds = []; $accountWechatId = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->value('wechatId'); - foreach ($adminFriendIds as $adminFriendId) { $adminFriend = Db::table('s2_wechat_friend')->where('id', $adminFriendId)->find(); if ($adminFriend && $adminFriend['ownerWechatId'] == $accountWechatId) { @@ -278,10 +277,10 @@ class WorkbenchGroupCreateJob } } } - + // 4.3 限制群主好友数量(按随机群人数) $limitedOwnerFriendIds = array_slice($ownerFriendIds, 0, $groupRandNum); - + // 4.4 创建群:管理员 + 群主成员 + 群主好友(从流量池筛选) $createFriendIds = array_merge($currentAdminFriendIds, $currentGroupMemberIds, $limitedOwnerFriendIds); @@ -379,12 +378,12 @@ class WorkbenchGroupCreateJob } // 从流量池用户中筛选出是管理员好友的用户 - $adminFriendsFromPool = Db::name('wechat_friendship')->alias('f') - ->join(['s2_wechat_account' => 'a'], 'f.ownerWechatId=a.wechatId') - ->where('f.companyId', $workbench->companyId) + $adminFriendsFromPool = Db::table('s2_wechat_friend')->alias('f') + ->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id') ->whereIn('f.wechatId', $batchUsers) - ->whereIn('f.ownerWechatId', $adminWechatIds) + ->whereIn('a.wechatId', $adminWechatIds) ->where('a.id', $wechatAccountId) + ->where('f.isDeleted', 0) ->field('f.id,f.wechatId') ->select();