代码提交
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"antd-mobile": "^5.39.1",
|
||||
"antd-mobile-icons": "^0.3.0",
|
||||
"axios": "^1.6.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
@@ -21,6 +22,7 @@
|
||||
"zustand": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
|
||||
33
Cunkebao/pnpm-lock.yaml
generated
33
Cunkebao/pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
||||
axios:
|
||||
specifier: ^1.6.7
|
||||
version: 1.11.0
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
@@ -54,6 +57,9 @@ importers:
|
||||
specifier: ^5.0.6
|
||||
version: 5.0.7(@types/react@19.1.10)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))
|
||||
devDependencies:
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
'@types/node':
|
||||
specifier: ^24.0.14
|
||||
version: 24.2.1
|
||||
@@ -489,36 +495,42 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||
@@ -669,56 +681,67 @@ packages:
|
||||
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
|
||||
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.46.2':
|
||||
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.46.2':
|
||||
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.46.2':
|
||||
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.46.2':
|
||||
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.46.2':
|
||||
resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
|
||||
@@ -747,6 +770,9 @@ packages:
|
||||
'@types/babel__traverse@7.28.0':
|
||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||
|
||||
'@types/crypto-js@4.2.2':
|
||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
@@ -1016,6 +1042,9 @@ packages:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
crypto-js@4.2.0:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
@@ -2958,6 +2987,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.28.2
|
||||
|
||||
'@types/crypto-js@4.2.2': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@24.2.1':
|
||||
@@ -3357,6 +3388,8 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
crypto-js@4.2.0: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
|
||||
89
Cunkebao/src/pages/mobile/test/api.ts
Normal file
89
Cunkebao/src/pages/mobile/test/api.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import axios from "axios";
|
||||
import { Toast } from "antd-mobile";
|
||||
import { generateSign } from "./utils/sign";
|
||||
|
||||
// API配置
|
||||
const API_BASE_URL = "https://ckbapi.quwanzhi.com/v1/api";
|
||||
const API_KEY = "v3pzy-zcfkg-96jio-7xgh6-14kio";
|
||||
|
||||
export interface SubmitLeadParams {
|
||||
phone: string;
|
||||
name: string;
|
||||
source: string;
|
||||
remark?: string;
|
||||
wechatId?: string;
|
||||
tags?: string;
|
||||
siteTags?: string;
|
||||
}
|
||||
|
||||
export interface SubmitLeadResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
data: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交线索到存客宝
|
||||
*/
|
||||
export async function submitLead(
|
||||
params: SubmitLeadParams,
|
||||
): Promise<SubmitLeadResponse> {
|
||||
try {
|
||||
// 生成时间戳(秒级)
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
// 构建请求参数
|
||||
const requestParams: Record<string, any> = {
|
||||
apiKey: API_KEY,
|
||||
timestamp,
|
||||
phone: params.phone,
|
||||
name: params.name,
|
||||
source: params.source,
|
||||
};
|
||||
|
||||
// 添加可选字段(只添加非空值)
|
||||
if (params.remark) {
|
||||
requestParams.remark = params.remark;
|
||||
}
|
||||
if (params.wechatId) {
|
||||
requestParams.wechatId = params.wechatId;
|
||||
}
|
||||
if (params.tags) {
|
||||
requestParams.tags = params.tags;
|
||||
}
|
||||
if (params.siteTags) {
|
||||
requestParams.siteTags = params.siteTags;
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
const sign = generateSign(requestParams, API_KEY);
|
||||
requestParams.sign = sign;
|
||||
|
||||
// 发送请求
|
||||
const response = await axios.post<SubmitLeadResponse>(
|
||||
`${API_BASE_URL}/scenarios`,
|
||||
requestParams,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 20000,
|
||||
},
|
||||
);
|
||||
|
||||
const result = response.data;
|
||||
|
||||
// 处理响应
|
||||
if (result.code === 200) {
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(result.message || "提交失败");
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errorMessage =
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
"网络请求失败,请稍后重试";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.modalContainer {
|
||||
background: #fff;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.formFooter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
173
Cunkebao/src/pages/mobile/test/components/TestFormModal.tsx
Normal file
173
Cunkebao/src/pages/mobile/test/components/TestFormModal.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, { useState } from "react";
|
||||
import { Popup, Form, Input, TextArea, Button, Toast } from "antd-mobile";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import styles from "./TestFormModal.module.scss";
|
||||
import { submitLead } from "../api";
|
||||
|
||||
interface TestFormModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit?: (values: TestFormValues) => void;
|
||||
}
|
||||
|
||||
export interface TestFormValues {
|
||||
phone: string;
|
||||
name: string;
|
||||
source: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
const TestFormModal: React.FC<TestFormModalProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setLoading(true);
|
||||
|
||||
// 调用API提交数据
|
||||
const result = await submitLead({
|
||||
phone: values.phone,
|
||||
name: values.name,
|
||||
source: values.source,
|
||||
remark: values.remark || undefined,
|
||||
});
|
||||
|
||||
// 调用提交回调(如果提供)
|
||||
if (onSubmit) {
|
||||
onSubmit(values as TestFormValues);
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
content: result.message || "提交成功",
|
||||
icon: "success",
|
||||
});
|
||||
|
||||
// 重置表单并关闭弹框
|
||||
form.resetFields();
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("提交失败:", error);
|
||||
Toast.show({
|
||||
content: error.message || "提交失败,请稍后重试",
|
||||
icon: "fail",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
form.resetFields();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Popup
|
||||
visible={visible}
|
||||
onMaskClick={handleClose}
|
||||
position="bottom"
|
||||
bodyStyle={{
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
maxHeight: "90vh",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<div className={styles.modalContainer}>
|
||||
{/* 头部 */}
|
||||
<div className={styles.modalHeader}>
|
||||
<h3 className={styles.modalTitle}>测试表单</h3>
|
||||
<CloseOutlined
|
||||
className={styles.closeIcon}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 表单内容 */}
|
||||
<div className={styles.modalContent}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
footer={
|
||||
<div className={styles.formFooter}>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
style={{ marginRight: 12 }}
|
||||
disabled={loading}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleSubmit}
|
||||
loading={loading}
|
||||
block
|
||||
>
|
||||
提交
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form.Item
|
||||
label="手机号"
|
||||
name="phone"
|
||||
rules={[
|
||||
{ required: true, message: "请输入手机号" },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: "请输入正确的手机号",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入手机号"
|
||||
type="tel"
|
||||
maxLength={11}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="姓名"
|
||||
name="name"
|
||||
rules={[
|
||||
{ required: true, message: "请输入姓名" },
|
||||
{ max: 20, message: "姓名不能超过20个字符" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入姓名" maxLength={20} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="来源"
|
||||
name="source"
|
||||
rules={[{ required: true, message: "请输入来源" }]}
|
||||
>
|
||||
<Input placeholder="请输入来源" maxLength={50} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="备注"
|
||||
name="remark"
|
||||
rules={[{ max: 200, message: "备注不能超过200个字符" }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="请输入备注(选填)"
|
||||
showCount
|
||||
maxLength={200}
|
||||
rows={3}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestFormModal;
|
||||
@@ -1,19 +1,28 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Card, Button, Space, Typography, Tag } from "antd";
|
||||
import {
|
||||
MessageOutlined,
|
||||
SelectOutlined,
|
||||
UploadOutlined,
|
||||
FormOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isDevelopment } from "@/utils/env";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import TestFormModal, { TestFormValues } from "./components/TestFormModal";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
const TestIndex: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [testFormVisible, setTestFormVisible] = useState(false);
|
||||
|
||||
const handleTestFormSubmit = (values: TestFormValues) => {
|
||||
// API调用已在TestFormModal内部完成
|
||||
// 这里可以添加额外的处理逻辑,如日志记录、数据分析等
|
||||
console.log("测试表单提交成功,数据:", values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout header={<NavCommon title="测试页面" />}>
|
||||
@@ -60,11 +69,31 @@ const TestIndex: React.FC = () => {
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card title="功能测试" size="small">
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<Button
|
||||
icon={<FormOutlined />}
|
||||
size="large"
|
||||
block
|
||||
onClick={() => setTestFormVisible(true)}
|
||||
>
|
||||
测试表单
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card title="说明" size="small">
|
||||
<Text>这里提供各种功能的测试页面,方便开发和调试。</Text>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 测试表单弹框 */}
|
||||
<TestFormModal
|
||||
visible={testFormVisible}
|
||||
onClose={() => setTestFormVisible(false)}
|
||||
onSubmit={handleTestFormSubmit}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
54
Cunkebao/src/pages/mobile/test/utils/sign.ts
Normal file
54
Cunkebao/src/pages/mobile/test/utils/sign.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 签名生成工具
|
||||
* 根据API文档的签名规则生成MD5签名
|
||||
*/
|
||||
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
* 生成MD5哈希值
|
||||
*/
|
||||
function md5(text: string): string {
|
||||
return CryptoJS.MD5(text).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* 根据API文档的签名规则生成MD5签名
|
||||
*/
|
||||
export function generateSign(
|
||||
params: Record<string, any>,
|
||||
apiKey: string,
|
||||
): string {
|
||||
// 第一步:移除 sign、apiKey、portrait
|
||||
const filteredParams: Record<string, any> = { ...params };
|
||||
delete filteredParams.sign;
|
||||
delete filteredParams.apiKey;
|
||||
delete filteredParams.portrait;
|
||||
|
||||
// 第二步:移除空值(null 和空字符串)
|
||||
const nonEmptyParams: Record<string, any> = {};
|
||||
for (const key in filteredParams) {
|
||||
const value = filteredParams[key];
|
||||
if (value !== null && value !== "" && value !== undefined) {
|
||||
nonEmptyParams[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 第三步:按参数名升序排序
|
||||
const sortedKeys = Object.keys(nonEmptyParams).sort();
|
||||
|
||||
// 第四步:拼接参数值
|
||||
let stringToSign = "";
|
||||
for (const key of sortedKeys) {
|
||||
stringToSign += String(nonEmptyParams[key]);
|
||||
}
|
||||
|
||||
// 第五步:第一次MD5
|
||||
const firstMd5 = md5(stringToSign);
|
||||
|
||||
// 第六步:拼接apiKey后第二次MD5
|
||||
const finalSign = md5(firstMd5 + apiKey);
|
||||
|
||||
return finalSign;
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
Select,
|
||||
Radio,
|
||||
} from "antd";
|
||||
|
||||
const { TextArea } = Input;
|
||||
import { fetchSocialMediaList, fetchPromotionSiteList } from "../index.api";
|
||||
|
||||
interface BasicSettingsProps {
|
||||
@@ -26,6 +28,19 @@ interface BasicSettingsProps {
|
||||
isLoop: number; // 0: 否, 1: 是
|
||||
pushType: number; // 0: 定时推送, 1: 立即推送
|
||||
status: number; // 0: 否, 1: 是
|
||||
isRandomTemplate?: number; // 是否随机模板:0=否,1=是
|
||||
postPushTags?: string[]; // 推送后标签数组
|
||||
targetType?: number; // 1=群推送,2=好友推送
|
||||
groupPushSubType?: number; // 1=群群发,2=群公告
|
||||
// 好友推送间隔设置
|
||||
friendIntervalMin?: number;
|
||||
friendIntervalMax?: number;
|
||||
messageIntervalMin?: number;
|
||||
messageIntervalMax?: number;
|
||||
// 群公告相关
|
||||
announcementContent?: string;
|
||||
enableAiRewrite?: number;
|
||||
aiRewritePrompt?: string;
|
||||
socialMediaId?: string;
|
||||
promotionSiteId?: string;
|
||||
};
|
||||
@@ -51,6 +66,8 @@ const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
isLoop: 0, // 0: 否, 1: 是
|
||||
pushType: 0, // 0: 定时推送, 1: 立即推送
|
||||
status: 0, // 0: 否, 1: 是
|
||||
targetType: 1, // 默认1=群推送
|
||||
groupPushSubType: 1, // 默认1=群群发
|
||||
socialMediaId: undefined,
|
||||
promotionSiteId: undefined,
|
||||
},
|
||||
@@ -69,6 +86,29 @@ const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
forceUpdate({});
|
||||
}, []);
|
||||
|
||||
// 监听 defaultValues 变化,更新表单值(用于编辑模式的数据回填)
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.setFieldsValue(defaultValues);
|
||||
forceUpdate({}); // 强制更新以刷新按钮状态
|
||||
|
||||
// 如果有社交媒体ID,加载对应的推广站点列表
|
||||
if (defaultValues.socialMediaId && defaultValues.socialMediaId !== "") {
|
||||
const socialMediaIdNum = Number(defaultValues.socialMediaId);
|
||||
if (!isNaN(socialMediaIdNum)) {
|
||||
setLoadingPromotionSite(true);
|
||||
fetchPromotionSiteList(socialMediaIdNum)
|
||||
.then(res => {
|
||||
setPromotionSiteList(res);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoadingPromotionSite(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [defaultValues, form]);
|
||||
|
||||
// 组件挂载时获取社交媒体列表
|
||||
useEffect(() => {
|
||||
setLoadingSocialMedia(true);
|
||||
@@ -144,6 +184,25 @@ const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
>
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送目标类型 - 暂时隐藏,但保留默认值 */}
|
||||
<Form.Item
|
||||
name="targetType"
|
||||
hidden
|
||||
initialValue={1}
|
||||
>
|
||||
<Input type="hidden" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 群推送子类型 - 暂时隐藏,但保留默认值 */}
|
||||
<Form.Item
|
||||
name="groupPushSubType"
|
||||
hidden
|
||||
initialValue={1}
|
||||
>
|
||||
<Input type="hidden" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送类型 */}
|
||||
<Form.Item
|
||||
label="推送类型"
|
||||
@@ -190,96 +249,202 @@ const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 每日推送 */}
|
||||
{/* 每日推送 - 群公告时隐藏 */}
|
||||
<Form.Item
|
||||
label="每日推送"
|
||||
name="maxPerDay"
|
||||
rules={[
|
||||
{ required: true, message: "请输入每日推送数量" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
message: "每日推送数量在1-100之间",
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
style={{ width: 120 }}
|
||||
addonAfter="条内容"
|
||||
/>
|
||||
{({ getFieldValue }) => {
|
||||
const isGroupAnnouncement = getFieldValue("targetType") === 1 && getFieldValue("groupPushSubType") === 2;
|
||||
return !isGroupAnnouncement ? (
|
||||
<Form.Item
|
||||
label="每日推送"
|
||||
name="maxPerDay"
|
||||
rules={[
|
||||
{ required: true, message: "请输入每日推送数量" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
message: "每日推送数量在1-100之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
style={{ width: 120 }}
|
||||
addonAfter="条内容"
|
||||
/>
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送顺序 */}
|
||||
{/* 推送顺序 - 群公告时隐藏 */}
|
||||
<Form.Item
|
||||
label="推送顺序"
|
||||
name="pushOrder"
|
||||
rules={[{ required: true, message: "请选择推送顺序" }]}
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
type={
|
||||
form.getFieldValue("pushOrder") == 1 ? "primary" : "default"
|
||||
}
|
||||
style={{ borderRadius: "6px 0 0 6px" }}
|
||||
onClick={() => handlePushOrderChange(1)}
|
||||
>
|
||||
按最早
|
||||
</Button>
|
||||
<Button
|
||||
type={
|
||||
form.getFieldValue("pushOrder") == 2 ? "primary" : "default"
|
||||
}
|
||||
style={{ borderRadius: "0 6px 6px 0", marginLeft: -1 }}
|
||||
onClick={() => handlePushOrderChange(2)}
|
||||
>
|
||||
按最新
|
||||
</Button>
|
||||
</div>
|
||||
{({ getFieldValue }) => {
|
||||
const isGroupAnnouncement = getFieldValue("targetType") === 1 && getFieldValue("groupPushSubType") === 2;
|
||||
return !isGroupAnnouncement ? (
|
||||
<Form.Item
|
||||
label="推送顺序"
|
||||
name="pushOrder"
|
||||
rules={[{ required: true, message: "请选择推送顺序" }]}
|
||||
>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
type={
|
||||
form.getFieldValue("pushOrder") == 1 ? "primary" : "default"
|
||||
}
|
||||
style={{ borderRadius: "6px 0 0 6px" }}
|
||||
onClick={() => handlePushOrderChange(1)}
|
||||
>
|
||||
按最早
|
||||
</Button>
|
||||
<Button
|
||||
type={
|
||||
form.getFieldValue("pushOrder") == 2 ? "primary" : "default"
|
||||
}
|
||||
style={{ borderRadius: "0 6px 6px 0", marginLeft: -1 }}
|
||||
onClick={() => handlePushOrderChange(2)}
|
||||
>
|
||||
按最新
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 京东联盟 */}
|
||||
<Form.Item label="京东联盟" style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
|
||||
<Form.Item name="socialMediaId" noStyle>
|
||||
<Select
|
||||
placeholder="请选择社交媒体"
|
||||
style={{ width: 200 }}
|
||||
loading={loadingSocialMedia}
|
||||
onChange={handleSocialMediaChange}
|
||||
options={socialMediaList.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* 京东联盟 - 仅群推送显示,群公告时隐藏 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const isGroupAnnouncement = getFieldValue("targetType") === 1 && getFieldValue("groupPushSubType") === 2;
|
||||
return getFieldValue("targetType") === 1 && !isGroupAnnouncement ? (
|
||||
<Form.Item label="京东联盟" style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
|
||||
<Form.Item name="socialMediaId" noStyle>
|
||||
<Select
|
||||
placeholder="请选择社交媒体"
|
||||
style={{ width: 200 }}
|
||||
loading={loadingSocialMedia}
|
||||
onChange={handleSocialMediaChange}
|
||||
options={socialMediaList.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="promotionSiteId" noStyle>
|
||||
<Select
|
||||
placeholder="请选择推广站点"
|
||||
style={{ width: 200 }}
|
||||
loading={loadingPromotionSite}
|
||||
disabled={!form.getFieldValue("socialMediaId")}
|
||||
options={promotionSiteList.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item name="promotionSiteId" noStyle>
|
||||
<Select
|
||||
placeholder="请选择推广站点"
|
||||
style={{ width: 200 }}
|
||||
loading={loadingPromotionSite}
|
||||
disabled={!form.getFieldValue("socialMediaId")}
|
||||
options={promotionSiteList.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否循环推送 */}
|
||||
{/* 是否随机模板 - 群公告时隐藏 */}
|
||||
<Form.Item
|
||||
label="是否循环推送"
|
||||
name="isLoop"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={checked => (checked ? 1 : 0)}
|
||||
getValueProps={value => ({ checked: value === 1 })}
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
<Switch />
|
||||
{({ getFieldValue }) => {
|
||||
const isGroupAnnouncement = getFieldValue("targetType") === 1 && getFieldValue("groupPushSubType") === 2;
|
||||
return !isGroupAnnouncement ? (
|
||||
<Form.Item
|
||||
label="是否随机模板"
|
||||
name="isRandomTemplate"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={checked => (checked ? 1 : 0)}
|
||||
getValueProps={value => ({ checked: value === 1 })}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送后标签 - 仅好友推送显示 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
return getFieldValue("targetType") === 2 ? (
|
||||
<Form.Item
|
||||
label="推送后标签"
|
||||
name="postPushTags"
|
||||
tooltip="推送后自动添加的标签,多个标签用逗号分隔"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入标签,多个用逗号分隔"
|
||||
onChange={e => {
|
||||
const tags = e.target.value
|
||||
.split(",")
|
||||
.map(tag => tag.trim())
|
||||
.filter(tag => tag.length > 0);
|
||||
form.setFieldValue("postPushTags", tags);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否循环推送 - 群推送和好友推送都显示,群公告时隐藏,好友推送默认为否 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const isGroupAnnouncement = getFieldValue("targetType") === 1 && getFieldValue("groupPushSubType") === 2;
|
||||
return !isGroupAnnouncement ? (
|
||||
<Form.Item
|
||||
label="是否循环推送"
|
||||
name="isLoop"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={checked => (checked ? 1 : 0)}
|
||||
getValueProps={value => ({ checked: value === 1 })}
|
||||
initialValue={0} // 默认为否
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否启用 */}
|
||||
@@ -293,6 +458,139 @@ const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送间隔设置 - 仅好友推送显示 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
return getFieldValue("targetType") === 2 ? (
|
||||
<>
|
||||
<Form.Item label="目标间间隔(秒)">
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<Form.Item
|
||||
name="friendIntervalMin"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请输入最小间隔" }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
placeholder="最小"
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Form.Item
|
||||
name="friendIntervalMax"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请输入最大间隔" }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
placeholder="最大"
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="消息间间隔(秒)">
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<Form.Item
|
||||
name="messageIntervalMin"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请输入最小间隔" }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
placeholder="最小"
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Form.Item
|
||||
name="messageIntervalMax"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请输入最大间隔" }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
placeholder="最大"
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 群公告设置 - 仅当targetType=1且groupPushSubType=2时显示 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.targetType !== currentValues.targetType ||
|
||||
prevValues.groupPushSubType !== currentValues.groupPushSubType
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
return getFieldValue("targetType") === 1 &&
|
||||
getFieldValue("groupPushSubType") === 2 ? (
|
||||
<>
|
||||
<Form.Item
|
||||
label="群公告内容"
|
||||
name="announcementContent"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群公告内容" },
|
||||
{ min: 1, max: 500, message: "群公告内容长度在1-500个字符之间" },
|
||||
]}
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="请输入群公告内容"
|
||||
maxLength={500}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="是否启用AI改写"
|
||||
name="enableAiRewrite"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={checked => (checked ? 1 : 0)}
|
||||
getValueProps={value => ({ checked: value === 1 })}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.enableAiRewrite !== currentValues.enableAiRewrite
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
return getFieldValue("enableAiRewrite") === 1 ? (
|
||||
<Form.Item
|
||||
label="AI改写提示词"
|
||||
name="aiRewritePrompt"
|
||||
rules={[
|
||||
{ required: true, message: "请输入AI改写提示词" },
|
||||
]}
|
||||
>
|
||||
<TextArea
|
||||
rows={3}
|
||||
placeholder="请输入AI改写提示词"
|
||||
/>
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送类型提示 */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
interface DeviceSelectorProps {
|
||||
selectedDevices: DeviceSelectionItem[];
|
||||
onPrevious: () => void;
|
||||
onNext: (data: {
|
||||
deviceGroups: string[];
|
||||
deviceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface DeviceSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const DeviceSelector = forwardRef<DeviceSelectorRef, DeviceSelectorProps>(
|
||||
({ selectedDevices, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
form.setFieldsValue({
|
||||
deviceGroups: selectedDevices.map(item => String(item.id)),
|
||||
});
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("DeviceSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
// 设备选择
|
||||
const handleDeviceSelect = (deviceGroupsOptions: DeviceSelectionItem[]) => {
|
||||
const deviceGroups = deviceGroupsOptions.map(item => String(item.id));
|
||||
form.setFieldValue("deviceGroups", deviceGroups);
|
||||
onNext({ deviceGroups, deviceGroupsOptions });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{ devices: selectedDevices }}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择设备
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要使用的设备
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="deviceGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个设备",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
placeholder="选择设备"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DeviceSelector.displayName = "DeviceSelector";
|
||||
|
||||
export default DeviceSelector;
|
||||
@@ -1,14 +1,25 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import React, { useImperativeHandle, forwardRef, useState } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import GroupSelection from "@/components/GroupSelection";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
import FriendSelection from "@/components/FriendSelection";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
interface GroupSelectorProps {
|
||||
selectedGroups: GroupSelectionItem[];
|
||||
targetType: number; // 1=群推送,2=好友推送
|
||||
selectedFriends?: any[];
|
||||
selectedPools?: any[];
|
||||
onPrevious: () => void;
|
||||
onNext: (data: {
|
||||
wechatGroups: string[];
|
||||
wechatGroupsOptions: GroupSelectionItem[];
|
||||
wechatGroups?: string[];
|
||||
wechatGroupsOptions?: GroupSelectionItem[];
|
||||
wechatFriends?: string[];
|
||||
wechatFriendsOptions?: any[];
|
||||
poolGroups?: string[];
|
||||
poolGroupsOptions?: any[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
@@ -18,17 +29,44 @@ export interface GroupSelectorRef {
|
||||
}
|
||||
|
||||
const GroupSelector = forwardRef<GroupSelectorRef, GroupSelectorProps>(
|
||||
({ selectedGroups, onNext }, ref) => {
|
||||
({ selectedGroups, targetType, selectedFriends = [], selectedPools = [], onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
const [friendsOptions, setFriendsOptions] = useState<FriendSelectionItem[]>(selectedFriends);
|
||||
const [poolsOptions, setPoolsOptions] = useState<PoolSelectionItem[]>(selectedPools);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
form.setFieldsValue({
|
||||
wechatGroups: selectedGroups.map(item => item.id),
|
||||
});
|
||||
await form.validateFields();
|
||||
if (targetType === 1) {
|
||||
// 群推送:必须选择群组
|
||||
form.setFieldsValue({
|
||||
wechatGroups: selectedGroups.map(item => item.id),
|
||||
});
|
||||
await form.validateFields(["wechatGroups"]);
|
||||
} else {
|
||||
// 好友推送:wechatFriends可选,但如果为空则必须选择流量池
|
||||
const friends = friendsOptions.map(item => String(item.id));
|
||||
const pools = poolsOptions.map(item => String(item.id));
|
||||
|
||||
form.setFieldsValue({
|
||||
wechatFriends: friends,
|
||||
poolGroups: pools,
|
||||
});
|
||||
|
||||
// 如果好友为空,则流量池必填
|
||||
if (friends.length === 0 && pools.length === 0) {
|
||||
form.setFields([
|
||||
{
|
||||
name: "poolGroups",
|
||||
errors: ["好友为空时,必须选择流量池"],
|
||||
},
|
||||
]);
|
||||
throw new Error("好友为空时,必须选择流量池");
|
||||
}
|
||||
|
||||
await form.validateFields(["wechatFriends", "poolGroups"]);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("GroupSelector 表单验证失败:", error);
|
||||
@@ -40,50 +78,138 @@ const GroupSelector = forwardRef<GroupSelectorRef, GroupSelectorProps>(
|
||||
},
|
||||
}));
|
||||
|
||||
// 群组选择
|
||||
// 群组选择(targetType=1)
|
||||
const handleGroupSelect = (wechatGroupsOptions: GroupSelectionItem[]) => {
|
||||
const wechatGroups = wechatGroupsOptions.map(item => item.id);
|
||||
form.setFieldValue("wechatGroups", wechatGroups);
|
||||
onNext({ wechatGroups, wechatGroupsOptions });
|
||||
};
|
||||
|
||||
// 好友选择(targetType=2)
|
||||
const handleFriendSelect = (friendsOptions: FriendSelectionItem[]) => {
|
||||
setFriendsOptions(friendsOptions);
|
||||
const wechatFriends = friendsOptions.map(item => String(item.id));
|
||||
form.setFieldValue("wechatFriends", wechatFriends);
|
||||
onNext({
|
||||
wechatFriends,
|
||||
wechatFriendsOptions: friendsOptions,
|
||||
poolGroups: poolsOptions.map(p => String(p.id)),
|
||||
poolGroupsOptions: poolsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
// 流量池选择(targetType=2)
|
||||
const handlePoolSelect = (poolsOptions: PoolSelectionItem[]) => {
|
||||
setPoolsOptions(poolsOptions);
|
||||
const poolGroups = poolsOptions.map(item => String(item.id));
|
||||
form.setFieldValue("poolGroups", poolGroups);
|
||||
onNext({
|
||||
wechatFriends: friendsOptions.map(f => String(f.id)),
|
||||
wechatFriendsOptions: friendsOptions,
|
||||
poolGroups,
|
||||
poolGroupsOptions: poolsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{ groups: selectedGroups }}
|
||||
initialValues={{
|
||||
groups: selectedGroups,
|
||||
friends: friendsOptions,
|
||||
pools: poolsOptions,
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择推送群组
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要推送消息的微信群组
|
||||
</p>
|
||||
</div>
|
||||
{targetType === 1 ? (
|
||||
// 群推送模式
|
||||
<>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择推送群组
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要推送消息的微信群组
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="wechatGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个群组",
|
||||
},
|
||||
{ type: "array", max: 50, message: "最多只能选择50个群组" },
|
||||
]}
|
||||
>
|
||||
<GroupSelection
|
||||
selectedOptions={selectedGroups}
|
||||
onSelect={handleGroupSelect}
|
||||
placeholder="选择要推送的群组"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="wechatGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个群组",
|
||||
},
|
||||
{ type: "array", max: 50, message: "最多只能选择50个群组" },
|
||||
]}
|
||||
>
|
||||
<GroupSelection
|
||||
selectedOptions={selectedGroups}
|
||||
onSelect={handleGroupSelect}
|
||||
placeholder="选择要推送的群组"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
) : (
|
||||
// 好友推送模式
|
||||
<>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择推送目标
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
可选择好友或流量池,好友为空时必须选择流量池
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 好友选择(可选) */}
|
||||
<Form.Item
|
||||
name="wechatFriends"
|
||||
label="选择好友(可选)"
|
||||
>
|
||||
<FriendSelection
|
||||
selectedOptions={friendsOptions}
|
||||
onSelect={handleFriendSelect}
|
||||
placeholder="选择要推送的好友"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 流量池选择(当好友为空时必选) */}
|
||||
<Form.Item
|
||||
name="poolGroups"
|
||||
label="选择流量池"
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator: (_, value) => {
|
||||
const friends = getFieldValue("wechatFriends") || [];
|
||||
if (friends.length === 0 && (!value || value.length === 0)) {
|
||||
return Promise.reject(new Error("好友为空时,必须选择流量池"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<PoolSelection
|
||||
selectedOptions={poolsOptions}
|
||||
onSelect={handlePoolSelect}
|
||||
placeholder="选择流量池"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
export function createGroupPushTask(data) {
|
||||
return request("/v1/workbench/create", { ...data, type: 3 }, "POST");
|
||||
}
|
||||
// 获取自动点赞任务详情
|
||||
export function fetchGroupPushTaskDetail(id: string) {
|
||||
return request("/v1/workbench/detail", { id }, "GET");
|
||||
// 创建群发工作台
|
||||
export function createGroupPushTask(data: any) {
|
||||
return request("/v1/workbench/create", data, "POST");
|
||||
}
|
||||
|
||||
export function updateGroupPushTask(data) {
|
||||
return request("/v1/workbench/update", { ...data, type: 3 }, "POST");
|
||||
// 更新群发工作台
|
||||
export function updateGroupPushTask(data: any) {
|
||||
return request("/v1/workbench/update", data, "POST");
|
||||
}
|
||||
|
||||
// 获取群发工作台详情
|
||||
export function fetchGroupPushTaskDetail(id: string) {
|
||||
return request("/v1/workbench/detail", { id }, "GET");
|
||||
}
|
||||
// 获取京东社交媒体列表
|
||||
export const fetchSocialMediaList = async () => {
|
||||
|
||||
@@ -27,9 +27,33 @@ export interface FormData {
|
||||
pushOrder: number; // 1: 按最早, 2: 按最新
|
||||
isLoop: number; // 0: 否, 1: 是
|
||||
pushType: number; // 0: 定时推送, 1: 立即推送
|
||||
status: number; // 0: 否, 1: 是
|
||||
status: number; // 0: 否, 1: 是(同时作为是否自动启动)
|
||||
isRandomTemplate?: number; // 是否随机模板:0=否,1=是
|
||||
postPushTags?: string[]; // 推送后标签数组
|
||||
contentGroups: string[];
|
||||
wechatGroups: string[];
|
||||
// 推送目标类型:1=群推送,2=好友推送
|
||||
targetType: number; // 默认1
|
||||
// 群推送子类型:1=群群发,2=群公告(仅当targetType=1时有效)
|
||||
groupPushSubType?: number; // 默认1
|
||||
// 好友推送相关
|
||||
wechatFriends?: string[]; // 当targetType=2时可选(可以为空)
|
||||
wechatFriendsOptions?: any[]; // 好友选项列表
|
||||
// 流量池(当wechatFriends为空时必须选择)
|
||||
poolGroups?: string[]; // 流量池ID列表
|
||||
poolGroupsOptions?: any[]; // 流量池选项列表
|
||||
// 好友推送间隔设置
|
||||
friendIntervalMin?: number; // 目标间最小间隔(秒)
|
||||
friendIntervalMax?: number; // 目标间最大间隔(秒)
|
||||
messageIntervalMin?: number; // 消息间最小间隔(秒)
|
||||
messageIntervalMax?: number; // 消息间最大间隔(秒)
|
||||
// 群公告相关(仅当targetType=1且groupPushSubType=2时)
|
||||
announcementContent?: string; // 群公告内容
|
||||
enableAiRewrite?: number; // 是否启用AI改写:0=否,1=是
|
||||
aiRewritePrompt?: string; // AI改写提示词
|
||||
// 设备选择
|
||||
deviceGroups?: string[]; // 设备ID列表
|
||||
deviceGroupsOptions?: any[]; // 设备选项列表
|
||||
// 京东联盟相关字段
|
||||
socialMediaId?: string;
|
||||
promotionSiteId?: string;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createGroupPushTask, fetchGroupPushTaskDetail } from "./index.api";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector";
|
||||
import GroupSelector, { GroupSelectorRef } from "./components/GroupSelector";
|
||||
import ContentSelector, {
|
||||
ContentSelectorRef,
|
||||
@@ -14,17 +15,49 @@ import type { FormData } from "./index.data";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
const steps = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择社群" },
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择内容库" },
|
||||
];
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
// 根据targetType和groupPushSubType动态生成步骤
|
||||
const getSteps = (targetType: number, groupPushSubType?: number) => {
|
||||
const baseSteps = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择设备" },
|
||||
];
|
||||
|
||||
if (targetType === 2) {
|
||||
// 好友推送:选择好友
|
||||
return [
|
||||
...baseSteps,
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择好友" },
|
||||
{ id: 4, title: "步骤 4", subtitle: "选择内容库" },
|
||||
];
|
||||
} else {
|
||||
// 群推送:选择社群
|
||||
const steps = [
|
||||
...baseSteps,
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择社群" },
|
||||
];
|
||||
// 群公告时不显示内容库步骤
|
||||
if (groupPushSubType !== 2) {
|
||||
steps.push({ id: 4, title: "步骤 4", subtitle: "选择内容库" });
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
};
|
||||
|
||||
const NewGroupPush: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 从 URL 参数获取推送类型
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlTargetType = urlParams.get("targetType");
|
||||
const urlGroupPushSubType = urlParams.get("groupPushSubType");
|
||||
const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
|
||||
DeviceSelectionItem[]
|
||||
>([]);
|
||||
const [wechatGroupsOptions, setWechatGroupsOptions] = useState<
|
||||
GroupSelectionItem[]
|
||||
>([]);
|
||||
@@ -34,43 +67,170 @@ const NewGroupPush: React.FC = () => {
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: "",
|
||||
startTime: "06:00", // 允许推送的开始时间
|
||||
startTime: "09:00", // 允许推送的开始时间
|
||||
dailyPushCount: 0, // 每日已推送次数
|
||||
endTime: "23:59", // 允许推送的结束时间
|
||||
endTime: "21:00", // 允许推送的结束时间
|
||||
maxPerDay: 20,
|
||||
pushOrder: 2, // 2: 按最新
|
||||
pushOrder: 1, // 1: 按最早
|
||||
isLoop: 0, // 0: 否, 1: 是
|
||||
pushType: 0, // 0: 定时推送, 1: 立即推送
|
||||
status: 0, // 0: 否, 1: 是
|
||||
status: 0, // 0: 否, 1: 是(同时作为是否自动启动)
|
||||
isRandomTemplate: 0, // 是否随机模板:0=否,1=是
|
||||
postPushTags: [], // 推送后标签数组
|
||||
wechatGroups: [],
|
||||
contentGroups: [],
|
||||
targetType: urlTargetType ? Number(urlTargetType) : 1, // 从URL参数获取,默认1=群推送
|
||||
groupPushSubType: urlGroupPushSubType ? Number(urlGroupPushSubType) : 1, // 从URL参数获取,默认1=群群发
|
||||
wechatFriends: [],
|
||||
wechatFriendsOptions: [],
|
||||
poolGroups: [],
|
||||
poolGroupsOptions: [],
|
||||
deviceGroups: [],
|
||||
// 好友推送间隔设置
|
||||
friendIntervalMin: 10, // 目标间最小间隔(秒)
|
||||
friendIntervalMax: 20, // 目标间最大间隔(秒)
|
||||
messageIntervalMin: 1, // 消息间最小间隔(秒)
|
||||
messageIntervalMax: 12, // 消息间最大间隔(秒)
|
||||
// 群公告相关
|
||||
announcementContent: "",
|
||||
enableAiRewrite: 0,
|
||||
aiRewritePrompt: "",
|
||||
});
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const deviceSelectorRef = useRef<DeviceSelectorRef>(null);
|
||||
const groupSelectorRef = useRef<GroupSelectorRef>(null);
|
||||
const contentSelectorRef = useRef<ContentSelectorRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
setIsEditMode(true);
|
||||
// 加载编辑数据
|
||||
const loadEditData = async () => {
|
||||
try {
|
||||
const res = await fetchGroupPushTaskDetail(id);
|
||||
const data = res?.data || res;
|
||||
const config = data?.config || {};
|
||||
|
||||
// 回填表单数据(支持接口字段名和数据库字段名的映射)
|
||||
// 数据库字段:groups, friends, trafficPools, contentLibraries, ownerWechatIds, devices
|
||||
// 接口字段:wechatGroups, wechatFriends, trafficPools, contentGroups, ownerWechatIds
|
||||
const groups = config.groups || config.wechatGroups || [];
|
||||
const friends = config.friends || config.wechatFriends || [];
|
||||
const trafficPools = config.trafficPools || config.poolGroups || [];
|
||||
const contentLibraries = config.contentLibraries || config.contentGroups || [];
|
||||
const ownerWechatIds = config.ownerWechatIds || config.deviceGroups || [];
|
||||
const devices = config.devices || [];
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
name: data.name || "",
|
||||
status: data.status ?? config.status ?? config.autoStart ?? 0, // status 和 autoStart 合并为 status
|
||||
targetType: config.targetType ?? 1,
|
||||
groupPushSubType: config.groupPushSubType ?? 1,
|
||||
pushType: config.pushType ?? 0, // 0=定时推送,1=立即推送
|
||||
startTime: config.startTime || "09:00",
|
||||
endTime: config.endTime || "21:00",
|
||||
maxPerDay: config.maxPerDay || 20,
|
||||
pushOrder: config.pushOrder || 1,
|
||||
isLoop: config.isLoop ?? 0,
|
||||
isRandomTemplate: config.isRandomTemplate ?? 0,
|
||||
postPushTags: config.postPushTags || [],
|
||||
// 支持数据库字段名和接口字段名的映射
|
||||
deviceGroups: [...ownerWechatIds, ...devices].map((id: any) => String(id)),
|
||||
wechatGroups: groups.map((id: any) => String(id)),
|
||||
wechatFriends: friends.map((id: any) => String(id)),
|
||||
poolGroups: trafficPools.map((id: any) => String(id)),
|
||||
contentGroups: contentLibraries.map((id: any) => String(id)),
|
||||
friendIntervalMin: config.friendIntervalMin || 10,
|
||||
friendIntervalMax: config.friendIntervalMax || 20,
|
||||
messageIntervalMin: config.messageIntervalMin || 1,
|
||||
messageIntervalMax: config.messageIntervalMax || 12,
|
||||
announcementContent: config.announcementContent || "",
|
||||
enableAiRewrite: config.enableAiRewrite ?? 0,
|
||||
aiRewritePrompt: config.aiRewritePrompt || "",
|
||||
socialMediaId: config.socialMediaId || "",
|
||||
promotionSiteId: config.promotionSiteId || "",
|
||||
}));
|
||||
|
||||
// 回填选项数据(支持多种字段名)
|
||||
// 设备选项:支持 deviceGroupsOptions, devicesOptions, ownerWechatOptions
|
||||
if (config.deviceGroupsOptions || config.devicesOptions || config.ownerWechatOptions) {
|
||||
setDeviceGroupsOptions(
|
||||
config.deviceGroupsOptions ||
|
||||
config.devicesOptions ||
|
||||
config.ownerWechatOptions ||
|
||||
[]
|
||||
);
|
||||
}
|
||||
// 群组选项:支持 wechatGroupsOptions, groupsOptions
|
||||
if (config.wechatGroupsOptions || config.groupsOptions) {
|
||||
setWechatGroupsOptions(config.wechatGroupsOptions || config.groupsOptions || []);
|
||||
}
|
||||
// 内容库选项:支持 contentGroupsOptions, contentLibrariesOptions
|
||||
if (config.contentGroupsOptions || config.contentLibrariesOptions) {
|
||||
setContentGroupsOptions(config.contentGroupsOptions || config.contentLibrariesOptions || []);
|
||||
}
|
||||
// 好友选项:支持 wechatFriendsOptions, friendsOptions
|
||||
if (config.wechatFriendsOptions || config.friendsOptions) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
wechatFriendsOptions: config.wechatFriendsOptions || config.friendsOptions || []
|
||||
}));
|
||||
}
|
||||
// 流量池选项:支持 poolGroupsOptions, trafficPoolsOptions
|
||||
if (config.poolGroupsOptions || config.trafficPoolsOptions) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
poolGroupsOptions: config.poolGroupsOptions || config.trafficPoolsOptions || []
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载编辑数据失败:", error);
|
||||
Toast.show({ content: "加载数据失败", position: "top" });
|
||||
}
|
||||
};
|
||||
loadEditData();
|
||||
}, [id]);
|
||||
|
||||
const handleBasicSettingsChange = (values: Partial<FormData>) => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
//群组选择
|
||||
const handleGroupsChange = (data: {
|
||||
wechatGroups: string[];
|
||||
wechatGroupsOptions: GroupSelectionItem[];
|
||||
//设备选择
|
||||
const handleDevicesChange = (data: {
|
||||
deviceGroups: string[];
|
||||
deviceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
wechatGroups: data.wechatGroups,
|
||||
deviceGroups: data.deviceGroups,
|
||||
}));
|
||||
setWechatGroupsOptions(data.wechatGroupsOptions);
|
||||
setDeviceGroupsOptions(data.deviceGroupsOptions);
|
||||
};
|
||||
|
||||
//群组选择(当targetType=1时)或好友/流量池选择(当targetType=2时)
|
||||
const handleGroupsChange = (data: {
|
||||
wechatGroups?: string[];
|
||||
wechatGroupsOptions?: GroupSelectionItem[];
|
||||
wechatFriends?: string[];
|
||||
wechatFriendsOptions?: any[];
|
||||
poolGroups?: string[];
|
||||
poolGroupsOptions?: any[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
wechatGroups: data.wechatGroups || [],
|
||||
wechatFriends: data.wechatFriends || [],
|
||||
poolGroups: data.poolGroups || [],
|
||||
wechatFriendsOptions: data.wechatFriendsOptions || [],
|
||||
poolGroupsOptions: data.poolGroupsOptions || [],
|
||||
}));
|
||||
if (data.wechatGroupsOptions) {
|
||||
setWechatGroupsOptions(data.wechatGroupsOptions);
|
||||
}
|
||||
};
|
||||
//内容库选择
|
||||
const handleLibrariesChange = (data: {
|
||||
@@ -83,57 +243,89 @@ const NewGroupPush: React.FC = () => {
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 调用 ContentSelector 的表单校验
|
||||
const isValid = (await contentSelectorRef.current?.validate()) || false;
|
||||
if (!isValid) return;
|
||||
// 群公告时不验证内容库选择器(因为步骤被隐藏了)
|
||||
const isGroupAnnouncement = formData.targetType === 1 && formData.groupPushSubType === 2;
|
||||
if (!isGroupAnnouncement) {
|
||||
// 调用 ContentSelector 的表单校验
|
||||
const isValid = (await contentSelectorRef.current?.validate()) || false;
|
||||
if (!isValid) return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
// 获取基础设置中的京东联盟数据
|
||||
const basicSettingsValues = basicSettingsRef.current?.getValues() || {};
|
||||
|
||||
// 构建 API 请求数据
|
||||
const apiData = {
|
||||
// 构建 API 请求数据(根据接口文档)
|
||||
const apiData: any = {
|
||||
name: formData.name,
|
||||
startTime: formData.startTime, // 允许推送的开始时间
|
||||
endTime: formData.endTime, // 允许推送的结束时间
|
||||
maxPerDay: formData.maxPerDay,
|
||||
pushOrder: formData.pushOrder,
|
||||
isLoop: formData.isLoop, // 0: 否, 1: 是
|
||||
pushType: formData.pushType, // 0: 定时推送, 1: 立即推送
|
||||
status: formData.status, // 0: 否, 1: 是
|
||||
wechatGroups: formData.wechatGroups,
|
||||
contentGroups: formData.contentGroups,
|
||||
// 京东联盟数据从基础设置中获取
|
||||
socialMediaId: basicSettingsValues.socialMediaId,
|
||||
promotionSiteId: basicSettingsValues.promotionSiteId,
|
||||
pushMode:
|
||||
formData.pushType === 1
|
||||
? ("immediate" as const)
|
||||
: ("scheduled" as const),
|
||||
messageType: "text" as const,
|
||||
messageContent: "",
|
||||
targetTags: [],
|
||||
pushInterval: 60,
|
||||
type: 3, // 群发工作台类型固定为3
|
||||
status: formData.status, // 0: 否, 1: 是(同时作为是否自动启动)
|
||||
targetType: formData.targetType, // 1=群推送,2=好友推送
|
||||
pushType: formData.pushType ?? 0, // 推送方式:0=定时推送,1=立即推送
|
||||
// 设备参数:参考自动建群的方式,使用deviceGroups
|
||||
deviceGroups: formData.deviceGroups?.map(id => Number(id)) || [], // 设备ID数组
|
||||
ownerWechatIds: formData.deviceGroups?.map(id => Number(id)) || [], // 设备ID数组(兼容字段)
|
||||
startTime: formData.startTime || "09:00", // 允许推送的开始时间
|
||||
endTime: formData.endTime || "21:00", // 允许推送的结束时间
|
||||
maxPerDay: formData.maxPerDay || 0,
|
||||
pushOrder: formData.pushOrder || 1,
|
||||
isRandomTemplate: formData.isRandomTemplate || 0,
|
||||
socialMediaId: basicSettingsValues.socialMediaId || "",
|
||||
promotionSiteId: basicSettingsValues.promotionSiteId || "",
|
||||
};
|
||||
|
||||
// 打印API请求数据,用于调试
|
||||
console.log("发送到API的数据:", apiData);
|
||||
// 群推送(targetType = 1)
|
||||
if (formData.targetType === 1) {
|
||||
apiData.groupPushSubType = formData.groupPushSubType || 1;
|
||||
apiData.wechatGroups = formData.wechatGroups.map(id => Number(id)); // 群ID数组
|
||||
apiData.isLoop = formData.isLoop || 0; // 群推送和好友推送都有循环推送
|
||||
// 群推送不打标签,不传递postPushTags
|
||||
// 群推送不传递间隔参数
|
||||
// 群公告不传递内容库,群群发才传递
|
||||
if (formData.groupPushSubType !== 2) {
|
||||
apiData.contentGroups = formData.contentGroups.map(id => Number(id)); // 内容库ID数组
|
||||
}
|
||||
|
||||
// 群公告(groupPushSubType = 2)
|
||||
if (formData.groupPushSubType === 2) {
|
||||
apiData.announcementContent = formData.announcementContent || "";
|
||||
apiData.enableAiRewrite = formData.enableAiRewrite || 0;
|
||||
if (formData.enableAiRewrite === 1) {
|
||||
apiData.aiRewritePrompt = formData.aiRewritePrompt || "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 好友推送(targetType = 2)
|
||||
apiData.wechatFriends = (formData.wechatFriends || []).map(id => Number(id));
|
||||
apiData.trafficPools = (formData.poolGroups || []).map(id => Number(id)); // 流量池ID数组
|
||||
apiData.isLoop = formData.isLoop || 0; // 好友推送默认为否(0)
|
||||
// 好友推送可以打标签
|
||||
apiData.postPushTags = formData.postPushTags || [];
|
||||
// 好友推送需要传递间隔参数
|
||||
apiData.friendIntervalMin = formData.friendIntervalMin || 10;
|
||||
apiData.friendIntervalMax = formData.friendIntervalMax || 20;
|
||||
apiData.messageIntervalMin = formData.messageIntervalMin || 1;
|
||||
apiData.messageIntervalMax = formData.messageIntervalMax || 12;
|
||||
// 好友推送需要传递内容库
|
||||
apiData.contentGroups = formData.contentGroups.map(id => Number(id)); // 内容库ID数组
|
||||
}
|
||||
|
||||
// 更新时需要传递id
|
||||
if (id) {
|
||||
apiData.id = Number(id);
|
||||
}
|
||||
|
||||
// 调用创建或更新 API
|
||||
if (id) {
|
||||
// 更新逻辑将在这里实现
|
||||
const { updateGroupPushTask } = await import("./index.api");
|
||||
await updateGroupPushTask(apiData);
|
||||
Toast.show({ content: "更新成功", position: "top" });
|
||||
navigate("/workspace/group-push");
|
||||
} else {
|
||||
createGroupPushTask(apiData)
|
||||
.then(() => {
|
||||
Toast.show({ content: "创建成功", position: "top" });
|
||||
navigate("/workspace/group-push");
|
||||
})
|
||||
.catch(() => {
|
||||
Toast.show({ content: "创建失败,请稍后重试", position: "top" });
|
||||
});
|
||||
await createGroupPushTask(apiData);
|
||||
Toast.show({ content: "创建成功", position: "top" });
|
||||
navigate("/workspace/group-push");
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.show({ content: "保存失败,请稍后重试", position: "top" });
|
||||
@@ -149,7 +341,7 @@ const NewGroupPush: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
if (currentStep < 3) {
|
||||
if (currentStep < 4) {
|
||||
try {
|
||||
let isValid = false;
|
||||
|
||||
@@ -167,10 +359,23 @@ const NewGroupPush: React.FC = () => {
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 调用 DeviceSelector 的表单校验
|
||||
isValid = (await deviceSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// 调用 GroupSelector 的表单校验
|
||||
isValid = (await groupSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
// 群公告时不显示内容库步骤,直接保存
|
||||
if (formData.targetType === 1 && formData.groupPushSubType === 2) {
|
||||
await handleSave();
|
||||
} else {
|
||||
setCurrentStep(4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -191,15 +396,20 @@ const NewGroupPush: React.FC = () => {
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 3 ? (
|
||||
<Button size="large" type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
)}
|
||||
{(() => {
|
||||
// 群公告时,步骤3就是最后一步
|
||||
const isGroupAnnouncement = formData.targetType === 1 && formData.groupPushSubType === 2;
|
||||
const isLastStep = isGroupAnnouncement ? currentStep === 3 : currentStep === 4;
|
||||
return isLastStep ? (
|
||||
<Button size="large" type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -211,7 +421,7 @@ const NewGroupPush: React.FC = () => {
|
||||
>
|
||||
<div style={{ padding: 12 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
<StepIndicator currentStep={currentStep} steps={getSteps(formData.targetType, formData.groupPushSubType)} />
|
||||
</div>
|
||||
<div>
|
||||
{currentStep === 1 && (
|
||||
@@ -226,6 +436,17 @@ const NewGroupPush: React.FC = () => {
|
||||
isLoop: formData.isLoop,
|
||||
status: formData.status,
|
||||
pushType: formData.pushType,
|
||||
targetType: formData.targetType,
|
||||
groupPushSubType: formData.groupPushSubType,
|
||||
isRandomTemplate: formData.isRandomTemplate,
|
||||
postPushTags: formData.postPushTags,
|
||||
friendIntervalMin: formData.friendIntervalMin,
|
||||
friendIntervalMax: formData.friendIntervalMax,
|
||||
messageIntervalMin: formData.messageIntervalMin,
|
||||
messageIntervalMax: formData.messageIntervalMax,
|
||||
announcementContent: formData.announcementContent,
|
||||
enableAiRewrite: formData.enableAiRewrite,
|
||||
aiRewritePrompt: formData.aiRewritePrompt,
|
||||
}}
|
||||
onNext={handleBasicSettingsChange}
|
||||
onSave={handleSave}
|
||||
@@ -233,18 +454,37 @@ const NewGroupPush: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<GroupSelector
|
||||
ref={groupSelectorRef}
|
||||
selectedGroups={wechatGroupsOptions}
|
||||
<DeviceSelector
|
||||
ref={deviceSelectorRef}
|
||||
selectedDevices={deviceGroupsOptions}
|
||||
onPrevious={() => setCurrentStep(1)}
|
||||
onNext={handleGroupsChange}
|
||||
onNext={handleDevicesChange}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<GroupSelector
|
||||
ref={groupSelectorRef}
|
||||
selectedGroups={wechatGroupsOptions}
|
||||
targetType={formData.targetType}
|
||||
selectedFriends={formData.wechatFriendsOptions || []}
|
||||
selectedPools={formData.poolGroupsOptions || []}
|
||||
onPrevious={() => setCurrentStep(2)}
|
||||
onNext={handleGroupsChange}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 4 && formData.targetType === 1 && formData.groupPushSubType !== 2 && (
|
||||
<ContentSelector
|
||||
ref={contentSelectorRef}
|
||||
selectedOptions={contentGroupsOptions}
|
||||
onPrevious={() => setCurrentStep(2)}
|
||||
onPrevious={() => setCurrentStep(3)}
|
||||
onNext={handleLibrariesChange}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 4 && formData.targetType === 2 && (
|
||||
<ContentSelector
|
||||
ref={contentSelectorRef}
|
||||
selectedOptions={contentGroupsOptions}
|
||||
onPrevious={() => setCurrentStep(3)}
|
||||
onNext={handleLibrariesChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -138,7 +138,12 @@ const GroupPush: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
navigate("/workspace/group-push/new");
|
||||
// 直接跳转到群消息推送(targetType=1, groupPushSubType=1)
|
||||
const params = new URLSearchParams({
|
||||
targetType: "1",
|
||||
groupPushSubType: "1",
|
||||
});
|
||||
navigate(`/workspace/group-push/new?${params.toString()}`);
|
||||
};
|
||||
|
||||
const filteredTasks = tasks.filter(task =>
|
||||
@@ -275,6 +280,7 @@ const GroupPush: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -106,3 +106,4 @@ class WorkbenchAutoLikeController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1896,11 +1896,35 @@ class WorkbenchController extends Controller
|
||||
throw new \Exception('消息间最小间隔不能大于最大间隔');
|
||||
}
|
||||
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
$contentGroups = $contentGroupsParam !== null
|
||||
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
|
||||
: $contentGroupsExisting;
|
||||
// 群公告(groupPushSubType = 2)时,contentGroups 可以为空,不需要验证
|
||||
if ($targetType === 1 && $groupPushSubType === 2) {
|
||||
// 群公告可以不传内容库,允许为空,不进行任何验证
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
if ($contentGroupsParam !== null) {
|
||||
// 如果传入了参数,尝试解析,但不验证格式和是否为空
|
||||
try {
|
||||
$contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误');
|
||||
} catch (\Exception $e) {
|
||||
// 群公告时忽略格式错误,使用空数组
|
||||
$contentGroups = [];
|
||||
}
|
||||
} else {
|
||||
// 如果没有传入参数,使用现有配置或空数组
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroups = $contentGroupsExisting;
|
||||
}
|
||||
} else {
|
||||
// 其他情况,正常处理并验证
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
$contentGroups = $contentGroupsParam !== null
|
||||
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
|
||||
: $contentGroupsExisting;
|
||||
// 其他情况,内容库为必填
|
||||
if (empty($contentGroups)) {
|
||||
throw new \Exception('请至少选择一个内容库');
|
||||
}
|
||||
}
|
||||
$data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : [];
|
||||
|
||||
@@ -18,454 +18,40 @@ class WorkbenchGroupPushController extends Controller
|
||||
const TYPE_GROUP_PUSH = 3; // 群消息推送
|
||||
|
||||
/**
|
||||
* 创建工作台
|
||||
* 获取群发统计数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function create()
|
||||
public function getGroupPushStats()
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
$timeRange = $this->request->param('timeRange', '7'); // 默认最近7天
|
||||
$contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选
|
||||
$userId = $this->request->userInfo['id'];
|
||||
|
||||
// 获取登录用户信息
|
||||
$userInfo = request()->userInfo;
|
||||
// 如果指定了工作台ID,则验证权限
|
||||
if (!empty($workbenchId)) {
|
||||
$workbench = Workbench::where([
|
||||
['id', '=', $workbenchId],
|
||||
['userId', '=', $userId],
|
||||
['type', '=', self::TYPE_GROUP_PUSH],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
// 获取请求参数
|
||||
$param = $this->request->post();
|
||||
|
||||
|
||||
// 根据业务默认值补全参数
|
||||
if (
|
||||
isset($param['type']) &&
|
||||
intval($param['type']) === self::TYPE_GROUP_PUSH
|
||||
) {
|
||||
if (empty($param['startTime'])) {
|
||||
$param['startTime'] = '09:00';
|
||||
}
|
||||
if (empty($param['endTime'])) {
|
||||
$param['endTime'] = '21:00';
|
||||
if (empty($workbench)) {
|
||||
return json(['code' => 404, 'msg' => '工作台不存在']);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证数据
|
||||
$validate = new WorkbenchValidate;
|
||||
if (!$validate->scene('create')->check($param)) {
|
||||
return json(['code' => 400, 'msg' => $validate->getError()]);
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 创建工作台基本信息
|
||||
$workbench = new Workbench;
|
||||
$workbench->name = $param['name'];
|
||||
$workbench->type = $param['type'];
|
||||
$workbench->status = !empty($param['status']) ? 1 : 0;
|
||||
$workbench->autoStart = !empty($param['autoStart']) ? 1 : 0;
|
||||
$workbench->userId = $userInfo['id'];
|
||||
$workbench->companyId = $userInfo['companyId'];
|
||||
$workbench->createTime = time();
|
||||
$workbench->updateTime = time();
|
||||
$workbench->save();
|
||||
|
||||
// 根据类型创建对应的配置
|
||||
switch ($param['type']) {
|
||||
case self::TYPE_AUTO_LIKE: // 自动点赞
|
||||
$config = new WorkbenchAutoLike;
|
||||
$config->workbenchId = $workbench->id;
|
||||
$config->interval = $param['interval'];
|
||||
$config->maxLikes = $param['maxLikes'];
|
||||
$config->startTime = $param['startTime'];
|
||||
$config->endTime = $param['endTime'];
|
||||
$config->contentTypes = json_encode($param['contentTypes']);
|
||||
$config->devices = json_encode($param['deviceGroups']);
|
||||
$config->friends = json_encode($param['wechatFriends']);
|
||||
// $config->targetGroups = json_encode($param['targetGroups']);
|
||||
// $config->tagOperator = $param['tagOperator'];
|
||||
$config->friendMaxLikes = $param['friendMaxLikes'];
|
||||
$config->friendTags = $param['friendTags'];
|
||||
$config->enableFriendTags = $param['enableFriendTags'];
|
||||
$config->createTime = time();
|
||||
$config->updateTime = time();
|
||||
$config->save();
|
||||
break;
|
||||
case self::TYPE_MOMENTS_SYNC: // 朋友圈同步
|
||||
$config = new WorkbenchMomentsSync;
|
||||
$config->workbenchId = $workbench->id;
|
||||
$config->syncInterval = $param['syncInterval'];
|
||||
$config->syncCount = $param['syncCount'];
|
||||
$config->syncType = $param['syncType'];
|
||||
$config->startTime = $param['startTime'];
|
||||
$config->endTime = $param['endTime'];
|
||||
$config->accountType = $param['accountType'];
|
||||
$config->devices = json_encode($param['deviceGroups']);
|
||||
$config->contentLibraries = json_encode($param['contentGroups'] ?? []);
|
||||
$config->createTime = time();
|
||||
$config->updateTime = time();
|
||||
$config->save();
|
||||
break;
|
||||
case self::TYPE_GROUP_PUSH: // 群消息推送
|
||||
$ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? []);
|
||||
$groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds);
|
||||
$groupPushData['workbenchId'] = $workbench->id;
|
||||
$groupPushData['createTime'] = time();
|
||||
$groupPushData['updateTime'] = time();
|
||||
$config = new WorkbenchGroupPush;
|
||||
$config->save($groupPushData);
|
||||
break;
|
||||
case self::TYPE_GROUP_CREATE: // 自动建群
|
||||
$config = new WorkbenchGroupCreate;
|
||||
$config->workbenchId = $workbench->id;
|
||||
$config->planType = !empty($param['planType']) ? $param['planType'] : 0;
|
||||
$config->executorId = !empty($param['executorId']) ? $param['executorId'] : 0;
|
||||
|
||||
$config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||
$config->startTime = $param['startTime'] ?? '';
|
||||
$config->endTime = $param['endTime'] ?? '';
|
||||
$config->groupSizeMin = intval($param['groupSizeMin'] ?? 3);
|
||||
$config->groupSizeMax = intval($param['groupSizeMax'] ?? 38);
|
||||
$config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20);
|
||||
$config->groupNameTemplate = $param['groupNameTemplate'] ?? '';
|
||||
$config->groupDescription = $param['groupDescription'] ?? '';
|
||||
$config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||
$config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段
|
||||
$admins = [];
|
||||
if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) {
|
||||
// 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用
|
||||
$adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId'];
|
||||
// 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID
|
||||
if (is_numeric($adminWechatId)) {
|
||||
$admins[] = intval($adminWechatId);
|
||||
} else {
|
||||
// 如果是wechatId字符串,需要查询对应的好友ID
|
||||
$friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find();
|
||||
if ($friend) {
|
||||
$admins[] = intval($friend['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果传入了admins参数,优先使用(兼容旧逻辑)
|
||||
if (!empty($param['admins']) && is_array($param['admins'])) {
|
||||
$admins = array_merge($admins, $param['admins']);
|
||||
}
|
||||
$config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$config->fixedWechatIds = json_encode($param['fixedWechatIds'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||
$config->createTime = time();
|
||||
$config->updateTime = time();
|
||||
$config->save();
|
||||
break;
|
||||
case self::TYPE_TRAFFIC_DISTRIBUTION: // 流量分发
|
||||
$config = new WorkbenchTrafficConfig;
|
||||
$config->workbenchId = $workbench->id;
|
||||
$config->distributeType = $param['distributeType'];
|
||||
$config->maxPerDay = $param['maxPerDay'];
|
||||
$config->timeType = $param['timeType'];
|
||||
$config->startTime = $param['startTime'];
|
||||
$config->endTime = $param['endTime'];
|
||||
$config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE);
|
||||
$config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE);
|
||||
$config->account = json_encode($param['accountGroups'], JSON_UNESCAPED_UNICODE);
|
||||
$config->createTime = time();
|
||||
$config->updateTime = time();
|
||||
$config->save();
|
||||
break;
|
||||
case self::TYPE_IMPORT_CONTACT: //联系人导入
|
||||
$config = new WorkbenchImportContact;
|
||||
$config->workbenchId = $workbench->id;
|
||||
$config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE);
|
||||
$config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE);
|
||||
$config->num = $param['num'];
|
||||
$config->clearContact = $param['clearContact'];
|
||||
$config->remark = $param['remark'];
|
||||
$config->startTime = $param['startTime'];
|
||||
$config->endTime = $param['endTime'];
|
||||
$config->createTime = time();
|
||||
$config->save();
|
||||
break;
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return json(['code' => 200, 'msg' => '创建成功', 'data' => ['id' => $workbench->id]]);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作台列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$type = $this->request->param('type', '');
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
// 计算时间范围
|
||||
$days = intval($timeRange);
|
||||
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days")));
|
||||
$endTime = time();
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['companyId', '=', $this->request->userInfo['companyId']],
|
||||
['isDel', '=', 0]
|
||||
['wgpi.createTime', '>=', $startTime],
|
||||
['wgpi.createTime', '<=', $endTime]
|
||||
];
|
||||
|
||||
if (empty($this->request->userInfo['isAdmin'])) {
|
||||
$where[] = ['userId', '=', $this->request->userInfo['id']];
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 添加类型筛选
|
||||
if ($type !== '') {
|
||||
$where[] = ['type', '=', $type];
|
||||
}
|
||||
|
||||
// 添加名称模糊搜索
|
||||
if ($keyword !== '') {
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 定义关联关系
|
||||
$with = [
|
||||
'autoLike' => function ($query) {
|
||||
$query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends');
|
||||
},
|
||||
'momentsSync' => function ($query) {
|
||||
$query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries');
|
||||
},
|
||||
'trafficConfig' => function ($query) {
|
||||
$query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account');
|
||||
},
|
||||
'groupPush' => function ($query) {
|
||||
$query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt');
|
||||
},
|
||||
'groupCreate' => function ($query) {
|
||||
$query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins');
|
||||
},
|
||||
'importContact' => function ($query) {
|
||||
$query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime');
|
||||
},
|
||||
'user' => function ($query) {
|
||||
$query->field('id,username');
|
||||
}
|
||||
];
|
||||
|
||||
$list = Workbench::where($where)
|
||||
->with($with)
|
||||
->field('id,companyId,name,type,status,autoStart,userId,createTime,updateTime')
|
||||
->order('id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select()
|
||||
->each(function ($item) {
|
||||
// 处理配置信息
|
||||
switch ($item->type) {
|
||||
case self::TYPE_AUTO_LIKE:
|
||||
if (!empty($item->autoLike)) {
|
||||
$item->config = $item->autoLike;
|
||||
$item->config->devices = json_decode($item->config->devices, true);
|
||||
$item->config->contentTypes = json_decode($item->config->contentTypes, true);
|
||||
$item->config->friends = json_decode($item->config->friends, true);
|
||||
|
||||
// 添加今日点赞数
|
||||
$startTime = strtotime(date('Y-m-d') . ' 00:00:00');
|
||||
$endTime = strtotime(date('Y-m-d') . ' 23:59:59');
|
||||
$todayLikeCount = Db::name('workbench_auto_like_item')
|
||||
->where('workbenchId', $item->id)
|
||||
->whereTime('createTime', 'between', [$startTime, $endTime])
|
||||
->count();
|
||||
|
||||
// 添加总点赞数
|
||||
$totalLikeCount = Db::name('workbench_auto_like_item')
|
||||
->where('workbenchId', $item->id)
|
||||
->count();
|
||||
|
||||
$item->config->todayLikeCount = $todayLikeCount;
|
||||
$item->config->totalLikeCount = $totalLikeCount;
|
||||
}
|
||||
unset($item->autoLike, $item->auto_like);
|
||||
break;
|
||||
case self::TYPE_MOMENTS_SYNC:
|
||||
if (!empty($item->momentsSync)) {
|
||||
$item->config = $item->momentsSync;
|
||||
$item->config->devices = json_decode($item->config->devices, true);
|
||||
$item->config->contentGroups = json_decode($item->config->contentLibraries, true);
|
||||
//同步记录
|
||||
$sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->count();
|
||||
$item->syncCount = $sendNum;
|
||||
$lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->order('id DESC')->value('createTime');
|
||||
$item->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--';
|
||||
|
||||
|
||||
// 获取内容库名称
|
||||
if (!empty($item->config->contentGroups)) {
|
||||
$libraryNames = ContentLibrary::where('id', 'in', $item->config->contentGroups)->select();
|
||||
$item->config->contentGroupsOptions = $libraryNames;
|
||||
} else {
|
||||
$item->config->contentGroupsOptions = [];
|
||||
}
|
||||
}
|
||||
unset($item->momentsSync, $item->moments_sync, $item->config->contentLibraries);
|
||||
break;
|
||||
case self::TYPE_GROUP_PUSH:
|
||||
if (!empty($item->groupPush)) {
|
||||
$item->config = $item->groupPush;
|
||||
$item->config->pushType = $item->config->pushType;
|
||||
$item->config->targetType = isset($item->config->targetType) ? intval($item->config->targetType) : 1; // 默认1=群推送
|
||||
$item->config->groupPushSubType = isset($item->config->groupPushSubType) ? intval($item->config->groupPushSubType) : 1; // 默认1=群群发
|
||||
$item->config->startTime = $item->config->startTime;
|
||||
$item->config->endTime = $item->config->endTime;
|
||||
$item->config->maxPerDay = $item->config->maxPerDay;
|
||||
$item->config->pushOrder = $item->config->pushOrder;
|
||||
$item->config->isLoop = $item->config->isLoop;
|
||||
$item->config->status = $item->config->status;
|
||||
$item->config->ownerWechatIds = json_decode($item->config->ownerWechatIds ?? '[]', true) ?: [];
|
||||
// 根据targetType解析不同的数据
|
||||
if ($item->config->targetType == 1) {
|
||||
// 群推送
|
||||
$item->config->wechatGroups = json_decode($item->config->groups, true) ?: [];
|
||||
$item->config->wechatFriends = [];
|
||||
// 群推送不需要devices字段
|
||||
// 群公告相关字段
|
||||
if ($item->config->groupPushSubType == 2) {
|
||||
$item->config->announcementContent = isset($item->config->announcementContent) ? $item->config->announcementContent : '';
|
||||
$item->config->enableAiRewrite = isset($item->config->enableAiRewrite) ? intval($item->config->enableAiRewrite) : 0;
|
||||
$item->config->aiRewritePrompt = isset($item->config->aiRewritePrompt) ? $item->config->aiRewritePrompt : '';
|
||||
}
|
||||
$item->config->trafficPools = [];
|
||||
} else {
|
||||
// 好友推送
|
||||
$item->config->wechatFriends = json_decode($item->config->friends, true) ?: [];
|
||||
$item->config->wechatGroups = [];
|
||||
$item->config->trafficPools = json_decode($item->config->trafficPools ?? '[]', true) ?: [];
|
||||
}
|
||||
$item->config->contentLibraries = json_decode($item->config->contentLibraries, true);
|
||||
$item->config->postPushTags = json_decode($item->config->postPushTags ?? '[]', true) ?: [];
|
||||
$item->config->lastPushTime = '';
|
||||
if (!empty($item->config->ownerWechatIds)) {
|
||||
$ownerWechatOptions = Db::name('wechat_account')
|
||||
->whereIn('id', $item->config->ownerWechatIds)
|
||||
->field('id,wechatId,nickName,avatar,alias')
|
||||
->select();
|
||||
$item->config->ownerWechatOptions = $ownerWechatOptions;
|
||||
} else {
|
||||
$item->config->ownerWechatOptions = [];
|
||||
}
|
||||
}
|
||||
unset($item->groupPush, $item->group_push);
|
||||
break;
|
||||
case self::TYPE_GROUP_CREATE:
|
||||
if (!empty($item->groupCreate)) {
|
||||
$item->config = $item->groupCreate;
|
||||
$item->config->devices = json_decode($item->config->devices, true);
|
||||
$item->config->poolGroups = json_decode($item->config->poolGroups, true);
|
||||
$item->config->wechatGroups = json_decode($item->config->wechatGroups, true);
|
||||
$item->config->admins = json_decode($item->config->admins ?? '[]', true) ?: [];
|
||||
|
||||
// 处理群管理员相关字段
|
||||
$item->config->groupAdminEnabled = !empty($item->config->admins) ? 1 : 0;
|
||||
|
||||
if (!empty($item->config->admins)) {
|
||||
$adminOptions = Db::table('s2_wechat_friend')->alias('wf')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
|
||||
->where('wf.id', 'in', $item->config->admins)
|
||||
->order('wf.id', 'desc')
|
||||
->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar')
|
||||
->select();
|
||||
$item->config->adminsOptions = $adminOptions;
|
||||
// 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显)
|
||||
$item->config->groupAdminWechatId = !empty($item->config->admins) ? $item->config->admins[0] : null;
|
||||
} else {
|
||||
$item->config->adminsOptions = [];
|
||||
$item->config->groupAdminWechatId = null;
|
||||
}
|
||||
}
|
||||
unset($item->groupCreate, $item->group_create);
|
||||
break;
|
||||
case self::TYPE_TRAFFIC_DISTRIBUTION:
|
||||
if (!empty($item->trafficConfig)) {
|
||||
$item->config = $item->trafficConfig;
|
||||
$item->config->devices = json_decode($item->config->devices, true);
|
||||
$item->config->poolGroups = json_decode($item->config->pools, true);
|
||||
$item->config->account = json_decode($item->config->account, true);
|
||||
$config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $item->id])->order('id DESC')->find();
|
||||
$item->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--';
|
||||
|
||||
//统计
|
||||
$labels = $item->config->poolGroups;
|
||||
$totalUsers = Db::table('s2_wechat_friend')->alias('wf')
|
||||
->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
|
||||
->where([
|
||||
['wf.isDeleted', '=', 0],
|
||||
['sa.departmentId', '=', $item->companyId]
|
||||
])
|
||||
->whereIn('wa.currentDeviceId', $item->config->devices);
|
||||
|
||||
if (!empty($labels) && count($labels) > 0) {
|
||||
$totalUsers = $totalUsers->where(function ($q) use ($labels) {
|
||||
foreach ($labels as $label) {
|
||||
$q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$totalUsers = $totalUsers->count();
|
||||
$totalAccounts = count($item->config->account);
|
||||
$dailyAverage = Db::name('workbench_traffic_config_item')
|
||||
->where('workbenchId', $item->id)
|
||||
->count();
|
||||
$day = (time() - strtotime($item->createTime)) / 86400;
|
||||
$day = intval($day);
|
||||
if ($dailyAverage > 0 && $totalAccounts > 0 && $day > 0) {
|
||||
$dailyAverage = $dailyAverage / $totalAccounts / $day;
|
||||
}
|
||||
$item->config->total = [
|
||||
'dailyAverage' => intval($dailyAverage),
|
||||
'totalAccounts' => $totalAccounts,
|
||||
'deviceCount' => count($item->config->devices),
|
||||
'poolCount' => !empty($item->config->poolGroups) ? count($item->config->poolGroups) : 'ALL',
|
||||
'totalUsers' => $totalUsers >> 0
|
||||
];
|
||||
}
|
||||
unset($item->trafficConfig, $item->traffic_config);
|
||||
break;
|
||||
|
||||
case self::TYPE_IMPORT_CONTACT:
|
||||
if (!empty($item->importContact)) {
|
||||
$item->config = $item->importContact;
|
||||
$item->config->devices = json_decode($item->config->devices, true);
|
||||
$item->config->poolGroups = json_decode($item->config->pools, true);
|
||||
}
|
||||
unset($item->importContact, $item->import_contact);
|
||||
break;
|
||||
}
|
||||
// 添加创建人名称
|
||||
$item['creatorName'] = $item->user ? $item->user->username : '';
|
||||
unset($item['user']); // 移除关联数据
|
||||
return $item;
|
||||
});
|
||||
|
||||
$total = Workbench::where($where)->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作台详情
|
||||
* @param int $id 工作台ID
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
|
||||
@@ -1001,7 +587,7 @@ class WorkbenchGroupPushController extends Controller
|
||||
case self::TYPE_GROUP_PUSH:
|
||||
$config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find();
|
||||
if ($config) {
|
||||
$ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config);
|
||||
$ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config, $param['deviceGroups'] ?? []);
|
||||
$groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds, $config);
|
||||
$groupPushData['updateTime'] = time();
|
||||
$config->save($groupPushData);
|
||||
@@ -1899,15 +1485,24 @@ class WorkbenchGroupPushController extends Controller
|
||||
* 规范化客服微信ID列表
|
||||
* @param mixed $ownerWechatIds
|
||||
* @param WorkbenchGroupPush|null $originalConfig
|
||||
* @param array $deviceGroups 设备ID数组
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array
|
||||
private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array
|
||||
{
|
||||
// 处理设备ID数组
|
||||
$deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误');
|
||||
|
||||
if ($ownerWechatIds === null) {
|
||||
$existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : [];
|
||||
if (empty($existing)) {
|
||||
throw new \Exception('请至少选择一个客服微信');
|
||||
// 如果原有配置为空,且没有传设备ID,则报错
|
||||
if (empty($existing) && empty($deviceGroupsList)) {
|
||||
throw new \Exception('请至少选择一个客服微信或设备');
|
||||
}
|
||||
// 如果原有配置为空但有设备ID,返回空数组(允许使用设备ID)
|
||||
if (empty($existing) && !empty($deviceGroupsList)) {
|
||||
return [];
|
||||
}
|
||||
return $existing;
|
||||
}
|
||||
@@ -1917,8 +1512,9 @@ class WorkbenchGroupPushController extends Controller
|
||||
}
|
||||
|
||||
$normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误');
|
||||
if (empty($normalized)) {
|
||||
throw new \Exception('请至少选择一个客服微信');
|
||||
// 如果 ownerWechatIds 为空,但传了设备ID,则允许(两个传一个即可)
|
||||
if (empty($normalized) && empty($deviceGroupsList)) {
|
||||
throw new \Exception('请至少选择一个客服微信或设备');
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
@@ -1960,6 +1556,14 @@ class WorkbenchGroupPushController extends Controller
|
||||
'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)),
|
||||
'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
// 处理设备ID数组(deviceGroups),保存到 devices 字段
|
||||
$deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : [];
|
||||
$deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null);
|
||||
$deviceGroups = $deviceGroupsParam !== null
|
||||
? $this->extractIdList($deviceGroupsParam, '设备参数格式错误')
|
||||
: $deviceGroupsExisting;
|
||||
$data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($data['friendIntervalMin'] > $data['friendIntervalMax']) {
|
||||
throw new \Exception('目标间最小间隔不能大于最大间隔');
|
||||
@@ -1968,11 +1572,35 @@ class WorkbenchGroupPushController extends Controller
|
||||
throw new \Exception('消息间最小间隔不能大于最大间隔');
|
||||
}
|
||||
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
$contentGroups = $contentGroupsParam !== null
|
||||
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
|
||||
: $contentGroupsExisting;
|
||||
// 群公告(groupPushSubType = 2)时,contentGroups 可以为空,不需要验证
|
||||
if ($targetType === 1 && $groupPushSubType === 2) {
|
||||
// 群公告可以不传内容库,允许为空,不进行任何验证
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
if ($contentGroupsParam !== null) {
|
||||
// 如果传入了参数,尝试解析,但不验证格式和是否为空
|
||||
try {
|
||||
$contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误');
|
||||
} catch (\Exception $e) {
|
||||
// 群公告时忽略格式错误,使用空数组
|
||||
$contentGroups = [];
|
||||
}
|
||||
} else {
|
||||
// 如果没有传入参数,使用现有配置或空数组
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroups = $contentGroupsExisting;
|
||||
}
|
||||
} else {
|
||||
// 其他情况,正常处理并验证
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
$contentGroups = $contentGroupsParam !== null
|
||||
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
|
||||
: $contentGroupsExisting;
|
||||
// 其他情况,内容库为必填
|
||||
if (empty($contentGroups)) {
|
||||
throw new \Exception('请至少选择一个内容库');
|
||||
}
|
||||
}
|
||||
$data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : [];
|
||||
@@ -2150,64 +1778,6 @@ class WorkbenchGroupPushController extends Controller
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取通讯录导入记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getImportContact()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
|
||||
$where = [
|
||||
['wici.workbenchId', '=', $workbenchId]
|
||||
];
|
||||
|
||||
// 查询发布记录
|
||||
$list = Db::name('workbench_import_contact_item')->alias('wici')
|
||||
->join('traffic_pool tp', 'tp.id = wici.poolId', 'left')
|
||||
->join('traffic_source tc', 'tc.identifier = tp.identifier', 'left')
|
||||
->join('wechat_account wa', 'wa.wechatId = tp.wechatId', 'left')
|
||||
->field([
|
||||
'wici.id',
|
||||
'wici.workbenchId',
|
||||
'wici.createTime',
|
||||
'tp.identifier',
|
||||
'tp.mobile',
|
||||
'tp.wechatId',
|
||||
'tc.name',
|
||||
'wa.nickName',
|
||||
'wa.avatar',
|
||||
'wa.alias',
|
||||
])
|
||||
->where($where)
|
||||
->order('tc.name DESC,wici.createTime DESC')
|
||||
->group('tp.identifier')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
}
|
||||
|
||||
|
||||
// 获取总记录数
|
||||
$total = Db::name('workbench_import_contact_item')->alias('wici')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群发统计数据
|
||||
* @return \think\response\Json
|
||||
@@ -3116,15 +2686,302 @@ class WorkbenchGroupPushController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已创建的群列表(自动建群)
|
||||
* @return \think\response\Json
|
||||
* 规范化客服微信ID列表
|
||||
* @param mixed $ownerWechatIds
|
||||
* @param WorkbenchGroupPush|null $originalConfig
|
||||
* @param array $deviceGroups 设备ID数组
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getCreatedGroupsList()
|
||||
private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array
|
||||
{
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 100);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
// 处理设备ID数组
|
||||
$deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误');
|
||||
|
||||
if ($ownerWechatIds === null) {
|
||||
$existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : [];
|
||||
// 如果原有配置为空,且没有传设备ID,则报错
|
||||
if (empty($existing) && empty($deviceGroupsList)) {
|
||||
throw new \Exception('请至少选择一个客服微信或设备');
|
||||
}
|
||||
// 如果原有配置为空但有设备ID,返回空数组(允许使用设备ID)
|
||||
if (empty($existing) && !empty($deviceGroupsList)) {
|
||||
return [];
|
||||
}
|
||||
return $existing;
|
||||
}
|
||||
|
||||
if (!is_array($ownerWechatIds)) {
|
||||
throw new \Exception('客服参数格式错误');
|
||||
}
|
||||
|
||||
$normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误');
|
||||
// 如果 ownerWechatIds 为空,但传了设备ID,则允许(两个传一个即可)
|
||||
if (empty($normalized) && empty($deviceGroupsList)) {
|
||||
throw new \Exception('请至少选择一个客服微信或设备');
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建群推送配置数据
|
||||
* @param array $param
|
||||
* @param array $ownerWechatIds
|
||||
* @param WorkbenchGroupPush|null $originalConfig
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array
|
||||
{
|
||||
$targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1;
|
||||
$targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1;
|
||||
|
||||
$groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1;
|
||||
$groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1;
|
||||
if (!in_array($groupPushSubType, [1, 2], true)) {
|
||||
$groupPushSubType = 1;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)),
|
||||
'targetType' => $targetType,
|
||||
'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''),
|
||||
'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''),
|
||||
'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)),
|
||||
'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1),
|
||||
'groupPushSubType' => $groupPushSubType,
|
||||
'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)),
|
||||
'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''),
|
||||
'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''),
|
||||
'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)),
|
||||
'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)),
|
||||
'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)),
|
||||
'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)),
|
||||
'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)),
|
||||
'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
// 处理设备ID数组(deviceGroups),保存到 devices 字段
|
||||
$deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : [];
|
||||
$deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null);
|
||||
$deviceGroups = $deviceGroupsParam !== null
|
||||
? $this->extractIdList($deviceGroupsParam, '设备参数格式错误')
|
||||
: $deviceGroupsExisting;
|
||||
$data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($data['friendIntervalMin'] > $data['friendIntervalMax']) {
|
||||
throw new \Exception('目标间最小间隔不能大于最大间隔');
|
||||
}
|
||||
if ($data['messageIntervalMin'] > $data['messageIntervalMax']) {
|
||||
throw new \Exception('消息间最小间隔不能大于最大间隔');
|
||||
}
|
||||
|
||||
// 群公告(groupPushSubType = 2)时,contentGroups 可以为空,不需要验证
|
||||
if ($targetType === 1 && $groupPushSubType === 2) {
|
||||
// 群公告可以不传内容库,允许为空,不进行任何验证
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
if ($contentGroupsParam !== null) {
|
||||
// 如果传入了参数,尝试解析,但不验证格式和是否为空
|
||||
try {
|
||||
$contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误');
|
||||
} catch (\Exception $e) {
|
||||
// 群公告时忽略格式错误,使用空数组
|
||||
$contentGroups = [];
|
||||
}
|
||||
} else {
|
||||
// 如果没有传入参数,使用现有配置或空数组
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroups = $contentGroupsExisting;
|
||||
}
|
||||
} else {
|
||||
// 其他情况,正常处理并验证
|
||||
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
|
||||
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
|
||||
$contentGroups = $contentGroupsParam !== null
|
||||
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
|
||||
: $contentGroupsExisting;
|
||||
// 其他情况,内容库为必填
|
||||
if (empty($contentGroups)) {
|
||||
throw new \Exception('请至少选择一个内容库');
|
||||
}
|
||||
}
|
||||
$data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : [];
|
||||
$postPushTagsParam = $this->getParamValue($param, 'postPushTags', null);
|
||||
$postPushTags = $postPushTagsParam !== null
|
||||
? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误')
|
||||
: $postPushTagsExisting;
|
||||
$data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($targetType === 1) {
|
||||
$data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0));
|
||||
|
||||
$groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : [];
|
||||
$wechatGroups = array_key_exists('wechatGroups', $param)
|
||||
? $this->extractIdList($param['wechatGroups'], '群参数格式错误')
|
||||
: $groupsExisting;
|
||||
if (empty($wechatGroups)) {
|
||||
throw new \Exception('群推送必须选择微信群');
|
||||
}
|
||||
$data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE);
|
||||
$data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE);
|
||||
$data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($groupPushSubType === 2) {
|
||||
$announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? '');
|
||||
if (empty($announcementContent)) {
|
||||
throw new \Exception('群公告必须输入公告内容');
|
||||
}
|
||||
$enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0));
|
||||
$aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? ''));
|
||||
if ($enableAiRewrite === 1 && $aiRewritePrompt === '') {
|
||||
throw new \Exception('启用AI智能话术改写时,必须输入改写提示词');
|
||||
}
|
||||
$data['announcementContent'] = $announcementContent;
|
||||
$data['enableAiRewrite'] = $enableAiRewrite;
|
||||
$data['aiRewritePrompt'] = $aiRewritePrompt;
|
||||
} else {
|
||||
$data['groupPushSubType'] = 1;
|
||||
$data['announcementContent'] = '';
|
||||
$data['enableAiRewrite'] = 0;
|
||||
$data['aiRewritePrompt'] = '';
|
||||
}
|
||||
} else {
|
||||
$data['isLoop'] = 0;
|
||||
$friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : [];
|
||||
$trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : [];
|
||||
|
||||
$friendTargets = array_key_exists('wechatFriends', $param)
|
||||
? $this->extractIdList($param['wechatFriends'], '好友参数格式错误')
|
||||
: $friendsExisting;
|
||||
$trafficPools = array_key_exists('trafficPools', $param)
|
||||
? $this->extractIdList($param['trafficPools'], '流量池参数格式错误')
|
||||
: $trafficPoolsExisting;
|
||||
|
||||
if (empty($friendTargets) && empty($trafficPools)) {
|
||||
throw new \Exception('好友推送需至少选择好友或流量池');
|
||||
}
|
||||
|
||||
$data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE);
|
||||
$data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE);
|
||||
$data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE);
|
||||
$data['groupPushSubType'] = 1;
|
||||
$data['announcementContent'] = '';
|
||||
$data['enableAiRewrite'] = 0;
|
||||
$data['aiRewritePrompt'] = '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数值,若不存在则返回默认值
|
||||
* @param array $param
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
private function getParamValue(array $param, string $key, $default)
|
||||
{
|
||||
return array_key_exists($key, $param) ? $param[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将值转换为整型布尔
|
||||
* @param mixed $value
|
||||
* @return int
|
||||
*/
|
||||
private function toBoolInt($value): int
|
||||
{
|
||||
return empty($value) ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数中提取ID列表
|
||||
* @param mixed $items
|
||||
* @param string $errorMessage
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function extractIdList($items, string $errorMessage = '参数格式错误'): array
|
||||
{
|
||||
if (!is_array($items)) {
|
||||
throw new \Exception($errorMessage);
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($items as $item) {
|
||||
if (is_array($item) && isset($item['id'])) {
|
||||
$item = $item['id'];
|
||||
}
|
||||
if ($item === '' || $item === null) {
|
||||
continue;
|
||||
}
|
||||
$ids[] = $item;
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码JSON数组
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function decodeJsonArray($value): array
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
$decoded = json_decode($value, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证内容是否包含链接
|
||||
* @param string $content 要检测的内容
|
||||
* @return bool
|
||||
*/
|
||||
private function containsLink($content)
|
||||
{
|
||||
// 定义各种链接的正则表达式模式
|
||||
$patterns = [
|
||||
// HTTP/HTTPS链接
|
||||
'/https?:\/\/[^\s]+/i',
|
||||
// 京东商品链接
|
||||
'/item\.jd\.com\/\d+/i',
|
||||
// 京东短链接
|
||||
'/u\.jd\.com\/[a-zA-Z0-9]+/i',
|
||||
// 淘宝商品链接
|
||||
'/item\.taobao\.com\/item\.htm\?id=\d+/i',
|
||||
// 天猫商品链接
|
||||
'/detail\.tmall\.com\/item\.htm\?id=\d+/i',
|
||||
// 淘宝短链接
|
||||
'/m\.tb\.cn\/[a-zA-Z0-9]+/i',
|
||||
// 拼多多链接
|
||||
'/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i',
|
||||
// 苏宁易购链接
|
||||
'/product\.suning\.com\/\d+\/\d+\.html/i',
|
||||
// 通用域名模式(包含常见电商域名)
|
||||
'/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i',
|
||||
// 通用短链接模式
|
||||
'/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i'
|
||||
];
|
||||
|
||||
// 遍历所有模式进行匹配
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $content)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($workbenchId)) {
|
||||
return json(['code' => 400, 'msg' => '工作台ID不能为空']);
|
||||
@@ -3247,12 +3104,7 @@ class WorkbenchGroupPushController extends Controller
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已创建群的详情(自动建群)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getCreatedGroupDetail()
|
||||
}
|
||||
{
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
$groupId = $this->request->param('groupId', 0);
|
||||
|
||||
@@ -311,3 +311,4 @@ class WorkbenchHelperController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,3 +67,4 @@ class WorkbenchImportContactController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -123,3 +123,4 @@ class WorkbenchMomentsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -307,3 +307,4 @@ class WorkbenchTrafficController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class Workbench extends Validate
|
||||
'wechatGroups' => 'checkGroupPushTarget|array|min:1', // 当targetType=1时必填
|
||||
'wechatFriends' => 'checkFriendPushTarget|array', // 当targetType=2时可选(可以为空)
|
||||
'ownerWechatId' => 'checkFriendPushService', // 当targetType=2且未选择好友/流量池时必填
|
||||
'contentGroups' => 'requireIf:type,3|array|min:1',
|
||||
'contentGroups' => 'checkContentGroups|array', // 群推送时必填,但群公告时可以为空
|
||||
// 群公告特有参数
|
||||
'announcementContent' => 'checkAnnouncementContent|max:5000', // 群公告内容(当groupPushSubType=2时必填)
|
||||
'enableAiRewrite' => 'checkEnableAiRewrite|in:0,1', // 是否启用AI智能话术改写
|
||||
@@ -106,7 +106,7 @@ class Workbench extends Validate
|
||||
'endTime.dateFormat' => '发布结束时间格式错误',
|
||||
'accountGroups.requireIf' => '请选择账号类型',
|
||||
'accountGroups.in' => '账号类型错误',
|
||||
'contentGroups.requireIf' => '请选择内容库',
|
||||
'contentGroups.checkContentGroups' => '群群发时必须选择内容库',
|
||||
'contentGroups.array' => '内容库格式错误',
|
||||
// 群消息推送相关提示
|
||||
'pushType.requireIf' => '请选择推送方式',
|
||||
@@ -383,4 +383,31 @@ class Workbench extends Validate
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证内容库(群推送时必填,但群公告时可以为空)
|
||||
*/
|
||||
protected function checkContentGroups($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
|
||||
|
||||
// 群公告(groupPushSubType=2)时,内容库可以为空,不需要验证
|
||||
if ($targetType == 1 && $groupPushSubType == 2) {
|
||||
// 群公告时允许为空,不进行验证
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他情况(群群发、好友推送),内容库必填
|
||||
if (!isset($value) || $value === null || $value === '') {
|
||||
return false;
|
||||
}
|
||||
if (!is_array($value) || count($value) < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
- **接口用途**:供第三方系统向【存客宝】上报客户线索(手机号 / 微信号等),用于后续的跟进、标签管理和画像分析。
|
||||
- **接口协议**:HTTP
|
||||
- **请求方式**:`POST`
|
||||
- **请求地址**: `http://ckbapi.quwanzhi.com/v1/api/scenarios`
|
||||
- **请求地址**: `https://ckbapi.quwanzhi.com/v1/api/scenarios`
|
||||
|
||||
> 具体 URL 以实际环境配置为准。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user