内容库可以选择设备
This commit is contained in:
@@ -26,4 +26,5 @@ export interface DeviceSelectionProps {
|
|||||||
showSelectedList?: boolean; // 新增
|
showSelectedList?: boolean; // 新增
|
||||||
readonly?: boolean; // 新增
|
readonly?: boolean; // 新增
|
||||||
deviceGroups?: any[]; // 传递设备组数据
|
deviceGroups?: any[]; // 传递设备组数据
|
||||||
|
singleSelect?: boolean; // 新增,是否单选模式
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
|||||||
showInput = true,
|
showInput = true,
|
||||||
showSelectedList = true,
|
showSelectedList = true,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
|
singleSelect = false,
|
||||||
}) => {
|
}) => {
|
||||||
// 弹窗控制
|
// 弹窗控制
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
@@ -37,6 +38,9 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
|||||||
// 获取显示文本
|
// 获取显示文本
|
||||||
const getDisplayText = () => {
|
const getDisplayText = () => {
|
||||||
if (selectedOptions.length === 0) return "";
|
if (selectedOptions.length === 0) return "";
|
||||||
|
if (singleSelect && selectedOptions.length > 0) {
|
||||||
|
return selectedOptions[0].memo || selectedOptions[0].wechatId || "已选择设备";
|
||||||
|
}
|
||||||
return `已选择 ${selectedOptions.length} 个设备`;
|
return `已选择 ${selectedOptions.length} 个设备`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,6 +183,7 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
|||||||
onClose={() => setRealVisible(false)}
|
onClose={() => setRealVisible(false)}
|
||||||
selectedOptions={selectedOptions}
|
selectedOptions={selectedOptions}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
singleSelect={singleSelect}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface SelectionPopupProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
selectedOptions: DeviceSelectionItem[];
|
selectedOptions: DeviceSelectionItem[];
|
||||||
onSelect: (devices: DeviceSelectionItem[]) => void;
|
onSelect: (devices: DeviceSelectionItem[]) => void;
|
||||||
|
singleSelect?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 20;
|
||||||
@@ -21,6 +22,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
singleSelect = false,
|
||||||
}) => {
|
}) => {
|
||||||
// 设备数据
|
// 设备数据
|
||||||
const [devices, setDevices] = useState<DeviceSelectionItem[]>([]);
|
const [devices, setDevices] = useState<DeviceSelectionItem[]>([]);
|
||||||
@@ -110,13 +112,23 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
|||||||
|
|
||||||
// 处理设备选择
|
// 处理设备选择
|
||||||
const handleDeviceToggle = (device: DeviceSelectionItem) => {
|
const handleDeviceToggle = (device: DeviceSelectionItem) => {
|
||||||
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
if (singleSelect) {
|
||||||
setTempSelectedOptions(
|
// 单选模式:如果已选中,则取消选择;否则替换为当前设备
|
||||||
tempSelectedOptions.filter(v => v.id !== device.id),
|
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||||||
);
|
setTempSelectedOptions([]);
|
||||||
|
} else {
|
||||||
|
setTempSelectedOptions([device]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const newSelectedOptions = [...tempSelectedOptions, device];
|
// 多选模式:原有的逻辑
|
||||||
setTempSelectedOptions(newSelectedOptions);
|
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||||||
|
setTempSelectedOptions(
|
||||||
|
tempSelectedOptions.filter(v => v.id !== device.id),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const newSelectedOptions = [...tempSelectedOptions, device];
|
||||||
|
setTempSelectedOptions(newSelectedOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,6 +191,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
|||||||
totalPages={totalPages}
|
totalPages={totalPages}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
selectedCount={tempSelectedOptions.length}
|
selectedCount={tempSelectedOptions.length}
|
||||||
|
singleSelect={singleSelect}
|
||||||
onPageChange={setCurrentPage}
|
onPageChange={setCurrentPage}
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
@@ -187,7 +200,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
isAllSelected={isCurrentPageAllSelected}
|
isAllSelected={isCurrentPageAllSelected}
|
||||||
onSelectAll={handleSelectAllCurrentPage}
|
onSelectAll={singleSelect ? undefined : handleSelectAllCurrentPage}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ export default function FriendSelection({
|
|||||||
{(selectedOptions || []).map(friend => (
|
{(selectedOptions || []).map(friend => (
|
||||||
<div key={friend.id} className={style.selectedListRow}>
|
<div key={friend.id} className={style.selectedListRow}>
|
||||||
<div className={style.selectedListRowContent}>
|
<div className={style.selectedListRowContent}>
|
||||||
<Avatar src={friend.friendAvatar} />
|
<Avatar src={friend.avatar || friend.friendAvatar} />
|
||||||
<div className={style.selectedListRowContentText}>
|
<div className={style.selectedListRowContentText}>
|
||||||
<div>{friend.friendName}</div>
|
<div>{friend.nickname || friend.friendName}</div>
|
||||||
<div>{friend.wechatId}</div>
|
<div>{friend.wechatId}</div>
|
||||||
</div>
|
</div>
|
||||||
{!readonly && (
|
{!readonly && (
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface PopupFooterProps {
|
|||||||
// 全选功能相关
|
// 全选功能相关
|
||||||
isAllSelected?: boolean;
|
isAllSelected?: boolean;
|
||||||
onSelectAll?: (checked: boolean) => void;
|
onSelectAll?: (checked: boolean) => void;
|
||||||
|
singleSelect?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopupFooter: React.FC<PopupFooterProps> = ({
|
const PopupFooter: React.FC<PopupFooterProps> = ({
|
||||||
@@ -26,20 +27,23 @@ const PopupFooter: React.FC<PopupFooterProps> = ({
|
|||||||
onConfirm,
|
onConfirm,
|
||||||
isAllSelected = false,
|
isAllSelected = false,
|
||||||
onSelectAll,
|
onSelectAll,
|
||||||
|
singleSelect = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 分页栏 */}
|
{/* 分页栏 */}
|
||||||
<div className={style.paginationRow}>
|
<div className={style.paginationRow}>
|
||||||
<div className={style.totalCount}>
|
{onSelectAll && (
|
||||||
<Checkbox
|
<div className={style.totalCount}>
|
||||||
checked={isAllSelected}
|
<Checkbox
|
||||||
onChange={e => onSelectAll(e.target.checked)}
|
checked={isAllSelected}
|
||||||
className={style.selectAllCheckbox}
|
onChange={e => onSelectAll(e.target.checked)}
|
||||||
>
|
className={style.selectAllCheckbox}
|
||||||
全选当前页
|
>
|
||||||
</Checkbox>
|
全选当前页
|
||||||
</div>
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className={style.paginationControls}>
|
<div className={style.paginationControls}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||||
@@ -61,7 +65,13 @@ const PopupFooter: React.FC<PopupFooterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.popupFooter}>
|
<div className={style.popupFooter}>
|
||||||
<div className={style.selectedCount}>已选择 {selectedCount} 条记录</div>
|
<div className={style.selectedCount}>
|
||||||
|
{singleSelect
|
||||||
|
? selectedCount > 0
|
||||||
|
? "已选择设备"
|
||||||
|
: "未选择设备"
|
||||||
|
: `已选择 ${selectedCount} 条记录`}
|
||||||
|
</div>
|
||||||
<div className={style.footerBtnGroup}>
|
<div className={style.footerBtnGroup}>
|
||||||
<Button color="primary" variant="filled" onClick={onCancel}>
|
<Button color="primary" variant="filled" onClick={onCancel}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
.form-page {
|
.form-page {
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
padding-bottom: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-main {
|
.form-main {
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 16px 0 0 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-card {
|
.form-card {
|
||||||
border-radius: 16px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
padding: 24px 18px 18px 18px;
|
padding: 16px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-tag {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
@@ -32,9 +46,23 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #222;
|
color: #222;
|
||||||
margin-top: 28px;
|
margin-top: 20px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
padding-left: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-block {
|
.section-block {
|
||||||
@@ -47,40 +75,78 @@
|
|||||||
.adm-tabs-header {
|
.adm-tabs-header {
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 12px;
|
||||||
|
padding: 4px;
|
||||||
}
|
}
|
||||||
.adm-tabs-tab {
|
.adm-tabs-tab {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 8px 0;
|
padding: 8px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.adm-tabs-tab-active {
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.adm-tabs-content {
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse {
|
.keyword-collapse {
|
||||||
margin-top: 12px;
|
.adm-collapse-panel-header {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.adm-collapse-panel-content {
|
.adm-collapse-panel-content {
|
||||||
padding-bottom: 8px;
|
padding: 16px 0 0 0;
|
||||||
background: #f8fafc;
|
background: transparent;
|
||||||
border-radius: 10px;
|
|
||||||
padding: 18px 14px 10px 14px;
|
|
||||||
margin-top: 2px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
margin-bottom: 22px;
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 8px;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
.adm-input {
|
}
|
||||||
min-height: 42px;
|
|
||||||
font-size: 15px;
|
.keyword-header {
|
||||||
border-radius: 7px;
|
display: flex;
|
||||||
margin-bottom: 2px;
|
align-items: center;
|
||||||
}
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyword-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyword-arrow {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyword-collapse .adm-collapse-panel-active .keyword-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-row,
|
.ai-row,
|
||||||
@@ -91,9 +157,53 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-desc {
|
.ai-desc {
|
||||||
color: #888;
|
color: #666;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-type-header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-type-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-type-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-type-btn {
|
||||||
|
padding: 10px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-row,
|
.date-row,
|
||||||
@@ -109,6 +219,179 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-limit-header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-limit-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-inputs {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #e5e6eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #222;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.enable-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enable-label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e5e6eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 8px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-tag {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
background: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-wechat-id {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-arrow {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-input-wrapper {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyword-select {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
min-height: 44px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
height: 48px !important;
|
height: 48px !important;
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { Input as AntdInput, Switch } from "antd";
|
import { Input as AntdInput, Switch, Tag } from "antd";
|
||||||
import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile";
|
import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile";
|
||||||
|
import { DownOutlined } from "@ant-design/icons";
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import FriendSelection from "@/components/FriendSelection";
|
import FriendSelection from "@/components/FriendSelection";
|
||||||
import GroupSelection from "@/components/GroupSelection";
|
import GroupSelection from "@/components/GroupSelection";
|
||||||
|
import DeviceSelection from "@/components/DeviceSelection";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
import request from "@/api/request";
|
import request from "@/api/request";
|
||||||
import { getContentLibraryDetail, updateContentLibrary } from "./api";
|
import { getContentLibraryDetail, updateContentLibrary } from "./api";
|
||||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||||
|
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||||
|
|
||||||
const { TextArea } = AntdInput;
|
const { TextArea } = AntdInput;
|
||||||
|
|
||||||
@@ -29,6 +32,15 @@ export default function ContentForm() {
|
|||||||
const isEdit = !!id;
|
const isEdit = !!id;
|
||||||
const [sourceType, setSourceType] = useState<"friends" | "groups">("friends");
|
const [sourceType, setSourceType] = useState<"friends" | "groups">("friends");
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>([]);
|
||||||
|
const [deviceSelectionVisible, setDeviceSelectionVisible] = useState(false);
|
||||||
|
|
||||||
|
// 处理设备选择(单选模式)
|
||||||
|
const handleDeviceSelect = (devices: DeviceSelectionItem[]) => {
|
||||||
|
// 单选模式:只保留最后一个选中的设备
|
||||||
|
setSelectedDevices(devices.length > 0 ? [devices[devices.length - 1]] : []);
|
||||||
|
setDeviceSelectionVisible(false);
|
||||||
|
};
|
||||||
const [friendsGroups, setSelectedFriends] = useState<string[]>([]);
|
const [friendsGroups, setSelectedFriends] = useState<string[]>([]);
|
||||||
const [friendsGroupsOptions, setSelectedFriendsOptions] = useState<
|
const [friendsGroupsOptions, setSelectedFriendsOptions] = useState<
|
||||||
FriendSelectionItem[]
|
FriendSelectionItem[]
|
||||||
@@ -64,6 +76,23 @@ export default function ContentForm() {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
setName(data.name || "");
|
setName(data.name || "");
|
||||||
setSourceType(data.sourceType === 1 ? "friends" : "groups");
|
setSourceType(data.sourceType === 1 ? "friends" : "groups");
|
||||||
|
// 详情接口中的采集设备数据可能在 devices / selectedDevices / deviceGroupsOptions 中
|
||||||
|
const deviceOptions: DeviceSelectionItem[] =
|
||||||
|
data.devices ||
|
||||||
|
data.selectedDevices ||
|
||||||
|
(data.deviceGroupsOptions
|
||||||
|
? (data.deviceGroupsOptions as any[]).map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
memo: item.memo,
|
||||||
|
imei: item.imei,
|
||||||
|
wechatId: item.wechatId,
|
||||||
|
status: item.alive === 1 ? "online" : "offline",
|
||||||
|
nickname: item.nickname,
|
||||||
|
avatar: item.avatar,
|
||||||
|
totalFriend: item.totalFriend,
|
||||||
|
}))
|
||||||
|
: []);
|
||||||
|
setSelectedDevices(deviceOptions || []);
|
||||||
setSelectedFriends(data.sourceFriends || []);
|
setSelectedFriends(data.sourceFriends || []);
|
||||||
setSelectedGroups(data.selectedGroups || []);
|
setSelectedGroups(data.selectedGroups || []);
|
||||||
setSelectedGroupsOptions(data.selectedGroupsOptions || []);
|
setSelectedGroupsOptions(data.selectedGroupsOptions || []);
|
||||||
@@ -72,7 +101,13 @@ export default function ContentForm() {
|
|||||||
setKeywordsExclude((data.keywordExclude || []).join(","));
|
setKeywordsExclude((data.keywordExclude || []).join(","));
|
||||||
setCatchType(data.catchType || ["text", "image", "video"]);
|
setCatchType(data.catchType || ["text", "image", "video"]);
|
||||||
setAIPrompt(data.aiPrompt || "");
|
setAIPrompt(data.aiPrompt || "");
|
||||||
setUseAI(!!data.aiPrompt);
|
// aiEnabled 为 AI 提示词开关,1 开启 0 关闭
|
||||||
|
if (typeof data.aiEnabled !== "undefined") {
|
||||||
|
setUseAI(data.aiEnabled === 1);
|
||||||
|
} else {
|
||||||
|
// 兼容旧数据,默认根据是否有 aiPrompt 判断
|
||||||
|
setUseAI(!!data.aiPrompt);
|
||||||
|
}
|
||||||
setEnabled(data.status === 1);
|
setEnabled(data.status === 1);
|
||||||
// 时间范围
|
// 时间范围
|
||||||
const start = data.timeStart || data.startTime;
|
const start = data.timeStart || data.startTime;
|
||||||
@@ -103,6 +138,7 @@ export default function ContentForm() {
|
|||||||
const payload = {
|
const payload = {
|
||||||
name,
|
name,
|
||||||
sourceType: sourceType === "friends" ? 1 : 2,
|
sourceType: sourceType === "friends" ? 1 : 2,
|
||||||
|
devices: selectedDevices.map(d => d.id),
|
||||||
friendsGroups: friendsGroups,
|
friendsGroups: friendsGroups,
|
||||||
wechatGroups: selectedGroups,
|
wechatGroups: selectedGroups,
|
||||||
groupMembers: {},
|
groupMembers: {},
|
||||||
@@ -115,7 +151,8 @@ export default function ContentForm() {
|
|||||||
.map(s => s.trim())
|
.map(s => s.trim())
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
catchType,
|
catchType,
|
||||||
aiPrompt,
|
aiPrompt,
|
||||||
|
aiEnabled: useAI ? 1 : 0,
|
||||||
timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0,
|
timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0,
|
||||||
startTime: dateRange[0] ? formatDate(dateRange[0]) : "",
|
startTime: dateRange[0] ? formatDate(dateRange[0]) : "",
|
||||||
endTime: dateRange[1] ? formatDate(dateRange[1]) : "",
|
endTime: dateRange[1] ? formatDate(dateRange[1]) : "",
|
||||||
@@ -178,7 +215,7 @@ export default function ContentForm() {
|
|||||||
onSubmit={e => e.preventDefault()}
|
onSubmit={e => e.preventDefault()}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<div className={style["form-section"]}>
|
<div className={style["form-card"]}>
|
||||||
<label className={style["form-label"]}>
|
<label className={style["form-label"]}>
|
||||||
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||||
内容库名称
|
内容库名称
|
||||||
@@ -191,8 +228,100 @@ export default function ContentForm() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style["section-title"]}>数据来源配置</div>
|
<div className={style["form-card"]}>
|
||||||
<div className={style["form-section"]}>
|
<label className={style["form-label"]}>
|
||||||
|
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||||
|
来源渠道
|
||||||
|
</label>
|
||||||
|
<Tag color="blue" className={style["source-tag"]}>
|
||||||
|
微信朋友圈
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["form-card"]}>
|
||||||
|
<label className={style["form-label"]}>
|
||||||
|
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||||
|
选择采集设备
|
||||||
|
</label>
|
||||||
|
{selectedDevices.length > 0 ? (
|
||||||
|
<div
|
||||||
|
className={style["device-card"]}
|
||||||
|
onClick={() => setDeviceSelectionVisible(true)}
|
||||||
|
>
|
||||||
|
<div className={style["device-avatar"]}>
|
||||||
|
{selectedDevices[0].avatar ? (
|
||||||
|
<img
|
||||||
|
src={selectedDevices[0].avatar}
|
||||||
|
alt="头像"
|
||||||
|
className={style["avatar-img"]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className={style["avatar-text"]}>
|
||||||
|
{(selectedDevices[0].memo ||
|
||||||
|
selectedDevices[0].wechatId ||
|
||||||
|
"设")[0]}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={style["device-info"]}>
|
||||||
|
<div className={style["device-name-row"]}>
|
||||||
|
<span className={style["device-name"]}>
|
||||||
|
{selectedDevices[0].nickname || selectedDevices[0].memo}
|
||||||
|
</span>
|
||||||
|
{selectedDevices[0].memo &&
|
||||||
|
selectedDevices[0].nickname &&
|
||||||
|
selectedDevices[0].memo !== selectedDevices[0].nickname && (
|
||||||
|
<span className={style["device-tag"]}>
|
||||||
|
{selectedDevices[0].memo}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={`${style["status-dot"]} ${
|
||||||
|
selectedDevices[0].status === "online"
|
||||||
|
? style["online"]
|
||||||
|
: style["offline"]
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style["device-wechat-id"]}>
|
||||||
|
微信ID: {selectedDevices[0].wechatId}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style["device-arrow"]}>
|
||||||
|
<DownOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={style["device-input-wrapper"]}>
|
||||||
|
<DeviceSelection
|
||||||
|
selectedOptions={selectedDevices}
|
||||||
|
onSelect={handleDeviceSelect}
|
||||||
|
placeholder="选择采集设备"
|
||||||
|
showInput={true}
|
||||||
|
showSelectedList={false}
|
||||||
|
singleSelect={true}
|
||||||
|
className={style["device-input"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 隐藏的设备选择组件,用于打开弹窗 */}
|
||||||
|
<div style={{ display: "none" }}>
|
||||||
|
<DeviceSelection
|
||||||
|
selectedOptions={selectedDevices}
|
||||||
|
onSelect={handleDeviceSelect}
|
||||||
|
placeholder="选择采集设备"
|
||||||
|
showInput={false}
|
||||||
|
showSelectedList={false}
|
||||||
|
singleSelect={true}
|
||||||
|
mode="dialog"
|
||||||
|
open={deviceSelectionVisible}
|
||||||
|
onOpenChange={setDeviceSelectionVisible}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["section-title"]}>采集内容配置</div>
|
||||||
|
<div className={style["form-card"]}>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={sourceType}
|
activeKey={sourceType}
|
||||||
onChange={key => setSourceType(key as "friends" | "groups")}
|
onChange={key => setSourceType(key as "friends" | "groups")}
|
||||||
@@ -203,6 +332,7 @@ export default function ContentForm() {
|
|||||||
selectedOptions={friendsGroupsOptions}
|
selectedOptions={friendsGroupsOptions}
|
||||||
onSelect={handleFriendsChange}
|
onSelect={handleFriendsChange}
|
||||||
placeholder="选择微信好友"
|
placeholder="选择微信好友"
|
||||||
|
deviceIds={selectedDevices.map(d => Number(d.id))}
|
||||||
/>
|
/>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab title="选择聊天群" key="groups">
|
<Tabs.Tab title="选择聊天群" key="groups">
|
||||||
@@ -215,54 +345,53 @@ export default function ContentForm() {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Collapse className={style["collapse"]}>
|
<div className={style["form-card"]}>
|
||||||
<Collapse.Panel
|
<Collapse className={style["keyword-collapse"]}>
|
||||||
key="keywords"
|
<Collapse.Panel
|
||||||
title={<span className={style["form-label"]}>关键词设置</span>}
|
key="keywords"
|
||||||
>
|
title={
|
||||||
<div className={style["form-section"]}>
|
<div className={style["keyword-header"]}>
|
||||||
<label className={style["form-label"]}>包含关键词</label>
|
<span className={style["keyword-title"]}>关键词设置</span>
|
||||||
<TextArea
|
<DownOutlined className={style["keyword-arrow"]} />
|
||||||
placeholder="多个关键词用逗号分隔"
|
</div>
|
||||||
value={keywordsInclude}
|
}
|
||||||
onChange={e => setKeywordsInclude(e.target.value)}
|
>
|
||||||
className={style["input"]}
|
<div className={style["form-section"]}>
|
||||||
autoSize={{ minRows: 2, maxRows: 4 }}
|
<label className={style["form-label"]}>包含关键词</label>
|
||||||
/>
|
<TextArea
|
||||||
</div>
|
placeholder="多个关键词用逗号分隔"
|
||||||
<div className={style["form-section"]}>
|
value={keywordsInclude}
|
||||||
<label className={style["form-label"]}>排除关键词</label>
|
onChange={e => setKeywordsInclude(e.target.value)}
|
||||||
<TextArea
|
className={style["input"]}
|
||||||
placeholder="多个关键词用逗号分隔"
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
value={keywordsExclude}
|
/>
|
||||||
onChange={e => setKeywordsExclude(e.target.value)}
|
</div>
|
||||||
className={style["input"]}
|
<div className={style["form-section"]}>
|
||||||
autoSize={{ minRows: 2, maxRows: 4 }}
|
<label className={style["form-label"]}>排除关键词</label>
|
||||||
/>
|
<TextArea
|
||||||
</div>
|
placeholder="多个关键词用逗号分隔"
|
||||||
</Collapse.Panel>
|
value={keywordsExclude}
|
||||||
</Collapse>
|
onChange={e => setKeywordsExclude(e.target.value)}
|
||||||
|
className={style["input"]}
|
||||||
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 采集内容类型 */}
|
{/* 采集内容类型 */}
|
||||||
<div className={style["section-title"]}>采集内容类型</div>
|
<div className={style["form-card"]}>
|
||||||
<div className={style["form-section"]}>
|
<div className={style["content-type-header"]}>
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
<span className={style["content-type-title"]}>采集内容类型</span>
|
||||||
|
</div>
|
||||||
|
<div className={style["content-type-buttons"]}>
|
||||||
{["text", "image", "video"].map(type => (
|
{["text", "image", "video"].map(type => (
|
||||||
<div
|
<button
|
||||||
key={type}
|
key={type}
|
||||||
style={{
|
className={`${style["content-type-btn"]} ${
|
||||||
display: "flex",
|
catchType.includes(type) ? style["active"] : ""
|
||||||
alignItems: "center",
|
}`}
|
||||||
gap: 8,
|
|
||||||
padding: "8px 12px",
|
|
||||||
border: "1px solid #d9d9d9",
|
|
||||||
borderRadius: "6px",
|
|
||||||
backgroundColor: catchType.includes(type)
|
|
||||||
? "#1890ff"
|
|
||||||
: "#fff",
|
|
||||||
color: catchType.includes(type) ? "#fff" : "#333",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCatchType(prev =>
|
setCatchType(prev =>
|
||||||
prev.includes(type)
|
prev.includes(type)
|
||||||
@@ -271,100 +400,101 @@ export default function ContentForm() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
{type === "text"
|
||||||
{type === "text"
|
? "文本"
|
||||||
? "文本"
|
: type === "image"
|
||||||
: type === "image"
|
? "图片"
|
||||||
? "图片"
|
: "视频"}
|
||||||
: "视频"}
|
</button>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style["section-title"]}>是否启用AI</div>
|
<div className={style["form-card"]}>
|
||||||
<div
|
<div
|
||||||
className={style["form-section"]}
|
className={style["form-section"]}
|
||||||
style={{ display: "flex", alignItems: "center", gap: 12 }}
|
style={{ display: "flex", alignItems: "center", gap: 12 }}
|
||||||
>
|
>
|
||||||
<Switch checked={useAI} onChange={setUseAI} />
|
<Switch checked={useAI} onChange={setUseAI} />
|
||||||
<span className={style["ai-desc"]}>
|
<span className={style["ai-desc"]}>
|
||||||
启用AI后,该内容库下的所有内容都会通过AI生成
|
启用后,该内容库下的内容会通过AI生成
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{useAI && (
|
{useAI && (
|
||||||
<div className={style["form-section"]}>
|
<div className={style["form-section"]}>
|
||||||
<label className={style["form-label"]}>AI提示词</label>
|
<label className={style["form-label"]}>AI提示词</label>
|
||||||
<TextArea
|
<TextArea
|
||||||
placeholder="请输入AI提示词"
|
placeholder="请输入AI提示词"
|
||||||
value={aiPrompt}
|
value={aiPrompt}
|
||||||
onChange={e => setAIPrompt(e.target.value)}
|
onChange={e => setAIPrompt(e.target.value)}
|
||||||
className={style["input"]}
|
className={style["input"]}
|
||||||
autoSize={{ minRows: 4, maxRows: 10 }}
|
autoSize={{ minRows: 4, maxRows: 10 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={style["section-title"]}>时间限制</div>
|
<div className={style["form-card"]}>
|
||||||
<div
|
<div className={style["time-limit-header"]}>
|
||||||
className={style["form-section"]}
|
<span className={style["time-limit-title"]}>时间限制</span>
|
||||||
style={{ display: "flex", gap: 12 }}
|
|
||||||
>
|
|
||||||
<label>开始时间</label>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<AntdInput
|
|
||||||
readOnly
|
|
||||||
value={dateRange[0] ? dateRange[0].toLocaleDateString() : ""}
|
|
||||||
placeholder="年/月/日"
|
|
||||||
className={style["input"]}
|
|
||||||
onClick={() => setShowStartPicker(true)}
|
|
||||||
/>
|
|
||||||
<DatePicker
|
|
||||||
visible={showStartPicker}
|
|
||||||
title="开始时间"
|
|
||||||
value={dateRange[0]}
|
|
||||||
onClose={() => setShowStartPicker(false)}
|
|
||||||
onConfirm={val => {
|
|
||||||
setDateRange([val, dateRange[1]]);
|
|
||||||
setShowStartPicker(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<label>结束时间</label>
|
<div className={style["date-inputs"]}>
|
||||||
<div style={{ flex: 1 }}>
|
<div className={style["date-item"]}>
|
||||||
<AntdInput
|
<label className={style["date-label"]}>开始时间</label>
|
||||||
readOnly
|
<AntdInput
|
||||||
value={dateRange[1] ? dateRange[1].toLocaleDateString() : ""}
|
readOnly
|
||||||
placeholder="年/月/日"
|
value={
|
||||||
className={style["input"]}
|
dateRange[0]
|
||||||
onClick={() => setShowEndPicker(true)}
|
? `${dateRange[0].getFullYear()}/${String(dateRange[0].getMonth() + 1).padStart(2, "0")}/${String(dateRange[0].getDate()).padStart(2, "0")}`
|
||||||
/>
|
: ""
|
||||||
<DatePicker
|
}
|
||||||
visible={showEndPicker}
|
placeholder="年/月/日"
|
||||||
title="结束时间"
|
className={style["date-input"]}
|
||||||
value={dateRange[1]}
|
onClick={() => setShowStartPicker(true)}
|
||||||
onClose={() => setShowEndPicker(false)}
|
/>
|
||||||
onConfirm={val => {
|
<DatePicker
|
||||||
setDateRange([dateRange[0], val]);
|
visible={showStartPicker}
|
||||||
setShowEndPicker(false);
|
title="开始时间"
|
||||||
}}
|
value={dateRange[0]}
|
||||||
/>
|
onClose={() => setShowStartPicker(false)}
|
||||||
|
onConfirm={val => {
|
||||||
|
setDateRange([val, dateRange[1]]);
|
||||||
|
setShowStartPicker(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style["date-item"]}>
|
||||||
|
<label className={style["date-label"]}>结束时间</label>
|
||||||
|
<AntdInput
|
||||||
|
readOnly
|
||||||
|
value={
|
||||||
|
dateRange[1]
|
||||||
|
? `${dateRange[1].getFullYear()}/${String(dateRange[1].getMonth() + 1).padStart(2, "0")}/${String(dateRange[1].getDate()).padStart(2, "0")}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
placeholder="年/月/日"
|
||||||
|
className={style["date-input"]}
|
||||||
|
onClick={() => setShowEndPicker(true)}
|
||||||
|
/>
|
||||||
|
<DatePicker
|
||||||
|
visible={showEndPicker}
|
||||||
|
title="结束时间"
|
||||||
|
value={dateRange[1]}
|
||||||
|
onClose={() => setShowEndPicker(false)}
|
||||||
|
onConfirm={val => {
|
||||||
|
setDateRange([dateRange[0], val]);
|
||||||
|
setShowEndPicker(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className={style["form-card"]}>
|
||||||
className={style["section-title"]}
|
<div className={style["enable-section"]}>
|
||||||
style={{
|
<span className={style["enable-label"]}>是否启用</span>
|
||||||
marginTop: 24,
|
<Switch checked={enabled} onChange={setEnabled} />
|
||||||
marginBottom: 8,
|
</div>
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>是否启用</span>
|
|
||||||
<Switch checked={enabled} onChange={setEnabled} />
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace app\cunkebao\controller;
|
namespace app\cunkebao\controller;
|
||||||
|
|
||||||
|
use app\common\model\Device as DeviceModel;
|
||||||
|
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||||
|
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||||
use app\cunkebao\model\ContentLibrary;
|
use app\cunkebao\model\ContentLibrary;
|
||||||
use app\cunkebao\model\ContentItem;
|
use app\cunkebao\model\ContentItem;
|
||||||
use library\s2\titleFavicon;
|
use library\s2\titleFavicon;
|
||||||
@@ -64,6 +67,7 @@ class ContentLibraryController extends Controller
|
|||||||
|
|
||||||
$keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'], 256) : json_encode([]);
|
$keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'], 256) : json_encode([]);
|
||||||
$keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'], 256) : json_encode([]);
|
$keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'], 256) : json_encode([]);
|
||||||
|
$devices = isset($param['devices']) ? json_encode($param['devices'], 256) : json_encode([]);
|
||||||
$sourceType = isset($param['sourceType']) ? $param['sourceType'] : 1;
|
$sourceType = isset($param['sourceType']) ? $param['sourceType'] : 1;
|
||||||
|
|
||||||
|
|
||||||
@@ -75,6 +79,7 @@ class ContentLibraryController extends Controller
|
|||||||
'sourceGroups' => $sourceType == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]), // 选择的微信群
|
'sourceGroups' => $sourceType == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]), // 选择的微信群
|
||||||
'groupMembers' => $sourceType == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]), // 群组成员
|
'groupMembers' => $sourceType == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]), // 群组成员
|
||||||
'catchType' => isset($param['catchType']) ? json_encode($param['catchType']) : json_encode([]), // 采集类型
|
'catchType' => isset($param['catchType']) ? json_encode($param['catchType']) : json_encode([]), // 采集类型
|
||||||
|
'devices' => $devices,
|
||||||
// 关键词配置
|
// 关键词配置
|
||||||
'keywordInclude' => $keywordInclude, // 包含的关键词
|
'keywordInclude' => $keywordInclude, // 包含的关键词
|
||||||
'keywordExclude' => $keywordExclude, // 排除的关键词
|
'keywordExclude' => $keywordExclude, // 排除的关键词
|
||||||
@@ -314,7 +319,7 @@ class ContentLibraryController extends Controller
|
|||||||
|
|
||||||
// 查询内容库信息
|
// 查询内容库信息
|
||||||
$library = ContentLibrary::where($where)
|
$library = ContentLibrary::where($where)
|
||||||
->field('id,name,sourceType,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
->field('id,name,sourceType,devices ,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
||||||
->find();
|
->find();
|
||||||
|
|
||||||
if (empty($library)) {
|
if (empty($library)) {
|
||||||
@@ -322,13 +327,14 @@ class ContentLibraryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理JSON字段转数组
|
// 处理JSON字段转数组
|
||||||
$library['friendsGroups'] = json_decode($library['sourceFriends'] ?: '[]', true);
|
$library['friendsGroups'] = json_decode($library['sourceFriends'] ?: [], true);
|
||||||
$library['wechatGroups'] = json_decode($library['sourceGroups'] ?: '[]', true);
|
$library['wechatGroups'] = json_decode($library['sourceGroups'] ?: [], true);
|
||||||
$library['keywordInclude'] = json_decode($library['keywordInclude'] ?: '[]', true);
|
$library['keywordInclude'] = json_decode($library['keywordInclude'] ?: [], true);
|
||||||
$library['keywordExclude'] = json_decode($library['keywordExclude'] ?: '[]', true);
|
$library['keywordExclude'] = json_decode($library['keywordExclude'] ?: [], true);
|
||||||
$library['groupMembers'] = json_decode($library['groupMembers'] ?: '[]', true);
|
$library['groupMembers'] = json_decode($library['groupMembers'] ?: [], true);
|
||||||
$library['catchType'] = json_decode($library['catchType'] ?: '[]', true);
|
$library['catchType'] = json_decode($library['catchType'] ?: [], true);
|
||||||
unset($library['sourceFriends'], $library['sourceGroups']);
|
$library['deviceGroups'] = json_decode($library['devices'] ?: [], true);
|
||||||
|
unset($library['sourceFriends'], $library['sourceGroups'],$library['devices']);
|
||||||
|
|
||||||
// 将时间戳转换为日期格式(精确到日)
|
// 将时间戳转换为日期格式(精确到日)
|
||||||
if (!empty($library['timeStart'])) {
|
if (!empty($library['timeStart'])) {
|
||||||
@@ -369,6 +375,32 @@ class ContentLibraryController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//获取设备信息
|
||||||
|
if (!empty($library['deviceGroups'])) {
|
||||||
|
$deviceList = DeviceModel::alias('d')
|
||||||
|
->field([
|
||||||
|
'd.id', 'd.imei', 'd.memo', 'd.alive',
|
||||||
|
'l.wechatId',
|
||||||
|
'a.nickname', 'a.alias', 'a.avatar', 'a.alias', '0 totalFriend'
|
||||||
|
])
|
||||||
|
->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId')
|
||||||
|
->leftJoin('wechat_account a', 'l.wechatId = a.wechatId')
|
||||||
|
->whereIn('d.id', $library['deviceGroups'])
|
||||||
|
->order('d.id desc')
|
||||||
|
->select();
|
||||||
|
|
||||||
|
foreach ($deviceList as &$device) {
|
||||||
|
$curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find();
|
||||||
|
$device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0;
|
||||||
|
}
|
||||||
|
unset($device);
|
||||||
|
$library['deviceGroupsOptions'] = $deviceList;
|
||||||
|
} else {
|
||||||
|
$library['deviceGroupsOptions'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return json([
|
return json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => '获取成功',
|
'msg' => '获取成功',
|
||||||
@@ -401,7 +433,8 @@ class ContentLibraryController extends Controller
|
|||||||
|
|
||||||
$where = [
|
$where = [
|
||||||
['companyId', '=', $this->request->userInfo['companyId']],
|
['companyId', '=', $this->request->userInfo['companyId']],
|
||||||
['isDel', '=', 0] // 只查询未删除的记录
|
['isDel', '=', 0], // 只查询未删除的记录
|
||||||
|
['id', '=', $param['id']]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (empty($this->request->userInfo['isAdmin'])) {
|
if (empty($this->request->userInfo['isAdmin'])) {
|
||||||
@@ -420,6 +453,7 @@ class ContentLibraryController extends Controller
|
|||||||
|
|
||||||
$keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'], 256) : json_encode([]);
|
$keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'], 256) : json_encode([]);
|
||||||
$keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'], 256) : json_encode([]);
|
$keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'], 256) : json_encode([]);
|
||||||
|
$devices = isset($param['devices']) ? json_encode($param['devices'], 256) : json_encode([]);
|
||||||
|
|
||||||
// 更新内容库基本信息
|
// 更新内容库基本信息
|
||||||
$library->name = $param['name'];
|
$library->name = $param['name'];
|
||||||
@@ -428,6 +462,7 @@ class ContentLibraryController extends Controller
|
|||||||
$library->sourceGroups = $param['sourceType'] == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]);
|
$library->sourceGroups = $param['sourceType'] == 2 && isset($param['wechatGroups']) ? json_encode($param['wechatGroups']) : json_encode([]);
|
||||||
$library->groupMembers = $param['sourceType'] == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]);
|
$library->groupMembers = $param['sourceType'] == 2 && isset($param['groupMembers']) ? json_encode($param['groupMembers']) : json_encode([]);
|
||||||
$library->catchType = isset($param['catchType']) ? json_encode($param['catchType']) : json_encode([]);// 采集类型
|
$library->catchType = isset($param['catchType']) ? json_encode($param['catchType']) : json_encode([]);// 采集类型
|
||||||
|
$library->devices = $devices;
|
||||||
$library->keywordInclude = $keywordInclude;
|
$library->keywordInclude = $keywordInclude;
|
||||||
$library->keywordExclude = $keywordExclude;
|
$library->keywordExclude = $keywordExclude;
|
||||||
$library->aiEnabled = isset($param['aiEnabled']) ? $param['aiEnabled'] : 0;
|
$library->aiEnabled = isset($param['aiEnabled']) ? $param['aiEnabled'] : 0;
|
||||||
@@ -437,8 +472,6 @@ class ContentLibraryController extends Controller
|
|||||||
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
||||||
$library->status = isset($param['status']) ? $param['status'] : 0;
|
$library->status = isset($param['status']) ? $param['status'] : 0;
|
||||||
$library->updateTime = time();
|
$library->updateTime = time();
|
||||||
|
|
||||||
|
|
||||||
$library->save();
|
$library->save();
|
||||||
|
|
||||||
Db::commit();
|
Db::commit();
|
||||||
@@ -597,8 +630,8 @@ class ContentLibraryController extends Controller
|
|||||||
$item['content'] = !empty($item['contentAi']) ? $item['contentAi'] : $item['content'];
|
$item['content'] = !empty($item['contentAi']) ? $item['contentAi'] : $item['content'];
|
||||||
|
|
||||||
// 处理JSON字段
|
// 处理JSON字段
|
||||||
$item['resUrls'] = json_decode($item['resUrls'] ?: '[]', true);
|
$item['resUrls'] = json_decode($item['resUrls'] ?: [], true);
|
||||||
$item['urls'] = json_decode($item['urls'] ?: '[]', true);
|
$item['urls'] = json_decode($item['urls'] ?: [], true);
|
||||||
|
|
||||||
// 格式化时间
|
// 格式化时间
|
||||||
if (!empty($item['createMomentTime']) && is_numeric($item['createMomentTime'])) {
|
if (!empty($item['createMomentTime']) && is_numeric($item['createMomentTime'])) {
|
||||||
@@ -798,8 +831,8 @@ class ContentLibraryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理JSON字段
|
// 处理JSON字段
|
||||||
$item['resUrls'] = json_decode($item['resUrls'] ?: '[]', true);
|
$item['resUrls'] = json_decode($item['resUrls'] ?: [], true);
|
||||||
$item['urls'] = json_decode($item['urls'] ?: '[]', true);
|
$item['urls'] = json_decode($item['urls'] ?: [], true);
|
||||||
|
|
||||||
// 添加内容类型的文字描述
|
// 添加内容类型的文字描述
|
||||||
$contentTypeMap = [
|
$contentTypeMap = [
|
||||||
@@ -1083,12 +1116,12 @@ class ContentLibraryController extends Controller
|
|||||||
// 预处理内容库数据
|
// 预处理内容库数据
|
||||||
foreach ($libraries as &$library) {
|
foreach ($libraries as &$library) {
|
||||||
// 解析JSON字段
|
// 解析JSON字段
|
||||||
$library['sourceFriends'] = json_decode($library['sourceFriends'] ?: '[]', true);
|
$library['sourceFriends'] = json_decode($library['sourceFriends'] ?: [], true);
|
||||||
$library['sourceGroups'] = json_decode($library['sourceGroups'] ?: '[]', true);
|
$library['sourceGroups'] = json_decode($library['sourceGroups'] ?: [], true);
|
||||||
$library['keywordInclude'] = json_decode($library['keywordInclude'] ?: '[]', true);
|
$library['keywordInclude'] = json_decode($library['keywordInclude'] ?: [], true);
|
||||||
$library['keywordExclude'] = json_decode($library['keywordExclude'] ?: '[]', true);
|
$library['keywordExclude'] = json_decode($library['keywordExclude'] ?: [], true);
|
||||||
$library['groupMembers'] = json_decode($library['groupMembers'] ?: '[]', true);
|
$library['groupMembers'] = json_decode($library['groupMembers'] ?: [], true);
|
||||||
$library['catchType'] = json_decode($library['catchType'] ?: '[]', true);
|
$library['catchType'] = json_decode($library['catchType'] ?: [], true);
|
||||||
}
|
}
|
||||||
unset($library); // 解除引用
|
unset($library); // 解除引用
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user