新版群添加功能
This commit is contained in:
@@ -33,14 +33,12 @@ import type {
|
||||
} from "./data";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
// 格式化金额显示(后端返回的是分,需要转换为元)
|
||||
// 格式化金额显示(后端返回的是元,直接格式化即可)
|
||||
const formatCurrency = (amount: number): string => {
|
||||
// 将分转换为元
|
||||
const yuan = amount / 100;
|
||||
if (yuan >= 10000) {
|
||||
return "¥" + (yuan / 10000).toFixed(2) + "万";
|
||||
if (amount >= 10000) {
|
||||
return "¥" + (amount / 10000).toFixed(2) + "万";
|
||||
}
|
||||
return "¥" + yuan.toFixed(2);
|
||||
return "¥" + amount.toFixed(2);
|
||||
};
|
||||
|
||||
const ChannelDetailPage: React.FC = () => {
|
||||
|
||||
18
Cunkebao/src/pages/mobile/workspace/group-create/form/api.ts
Normal file
18
Cunkebao/src/pages/mobile/workspace/group-create/form/api.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
// 创建自动建群任务
|
||||
export const createGroupCreate = (params: any) =>
|
||||
request("/v1/workbench/create", params, "POST");
|
||||
|
||||
// 更新自动建群任务
|
||||
export const updateGroupCreate = (params: any) =>
|
||||
request("/v1/workbench/update", params, "POST");
|
||||
|
||||
// 获取自动建群任务详情
|
||||
export const getGroupCreateDetail = (id: string) =>
|
||||
request("/v1/workbench/detail", { id }, "GET");
|
||||
|
||||
// 获取自动建群任务列表
|
||||
export const getGroupCreateList = (params: any) =>
|
||||
request("/v1/workbench/list", params, "GET");
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
.container {
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden; // 防止水平滚动
|
||||
|
||||
@media (max-width: 375px) {
|
||||
padding: 12px; // 小屏幕时减小padding
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-sizing: border-box; // 确保padding不会导致超出
|
||||
width: 100%;
|
||||
overflow: hidden; // 防止内容溢出
|
||||
|
||||
@media (max-width: 375px) {
|
||||
padding: 12px; // 小屏幕时减小padding
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.labelRequired {
|
||||
color: #ef4444;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行智能体选择区域
|
||||
.executorSelector {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #f8fafc;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
.executorContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.executorAvatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #e2e8f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.executorInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.executorName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.executorId {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.executorExpand {
|
||||
color: #64748b;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.statusDot {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #10b981;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// 固定微信号
|
||||
.wechatSelect {
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.wechatSelectInput {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #f1f5f9;
|
||||
color: #94a3b8;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selectInputWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.selectInputPlaceholder {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
flex: 1;
|
||||
|
||||
.wechatSelectInput.disabled & {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.selectInputArrow {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.selectedList {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.selectedItem {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.selectedItemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.selectedItemAvatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #e2e8f0;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.selectedItemInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.selectedItemName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.selectedItemId {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
color: #94a3b8;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
// 手动添加
|
||||
.manualAdd {
|
||||
border-top: 1px solid #f1f5f9;
|
||||
padding-top: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.manualAddLabel {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.manualAddInput {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.manualAddInputWrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.manualAddIcon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
color: #94a3b8;
|
||||
font-size: 18px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.manualAddInputField {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: 40px;
|
||||
padding-right: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
}
|
||||
|
||||
.manualAddButton {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #3b82f6;
|
||||
border: 1px solid #3b82f6;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
padding: 0;
|
||||
line-height: 1.2;
|
||||
|
||||
&:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.buttonText2 {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 已添加的微信号(带编号)
|
||||
.addedList {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.addedItem {
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.addedItemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.addedItemNumber {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #bfdbfe;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1e40af;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.addedItemInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.addedItemName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.addedItemId {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.addedItemStatus {
|
||||
font-size: 12px;
|
||||
color: #3b82f6;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.addedCount {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// 群管理员
|
||||
.groupAdminHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.groupAdminLabelWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.switchWrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.groupAdminHint {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
// 分组方式
|
||||
.groupMethod {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
// 覆盖antd Radio的默认样式
|
||||
:global(.ant-radio-group) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.groupMethodItem {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.groupMethodRadio {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.groupMethodContent {
|
||||
margin-left: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.groupMethodTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.groupMethodDesc {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// 群配置信息卡片
|
||||
.groupConfigCard {
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.groupConfigHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.groupConfigIcon {
|
||||
color: #3b82f6;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.groupConfigTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.groupConfigRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.groupConfigLabel {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.groupConfigValue {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #3b82f6;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// 错误提示
|
||||
.errorTip {
|
||||
background: #fff;
|
||||
border: 1px solid #fca5a5;
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.errorIcon {
|
||||
color: #ef4444;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.errorText {
|
||||
font-size: 14px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
// 群人数配置
|
||||
.groupSizeConfig {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.groupSizeRow {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap; // 小屏幕时允许换行
|
||||
|
||||
@media (max-width: 375px) {
|
||||
flex-direction: column; // 小屏幕时垂直排列
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.groupSizeItem {
|
||||
flex: 1;
|
||||
min-width: 0; // 防止flex item超出容器
|
||||
|
||||
@media (max-width: 375px) {
|
||||
flex: none; // 小屏幕时取消flex,占满宽度
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.groupSizeLabel {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #1e293b;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
word-wrap: break-word; // 允许长文本换行
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@media (max-width: 375px) {
|
||||
font-size: 13px; // 小屏幕时稍微减小字体
|
||||
}
|
||||
}
|
||||
|
||||
// 执行时间
|
||||
.timeRangeContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap; // 小屏幕时允许换行
|
||||
|
||||
@media (max-width: 375px) {
|
||||
flex-direction: column; // 小屏幕时垂直排列
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeInputWrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
min-width: 0; // 防止flex item超出容器
|
||||
|
||||
@media (max-width: 375px) {
|
||||
flex: none; // 小屏幕时取消flex,占满宽度
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.timeSeparator {
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin: 0 4px;
|
||||
|
||||
@media (max-width: 375px) {
|
||||
display: none; // 小屏幕时隐藏分隔符
|
||||
}
|
||||
}
|
||||
|
||||
.timeInput {
|
||||
width: 100%;
|
||||
padding: 10px 12px 10px 36px; // 左边留出图标空间
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box; // 确保padding不会导致超出
|
||||
|
||||
&:focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.timeIcon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #94a3b8;
|
||||
font-size: 16px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
import React, { useImperativeHandle, forwardRef, useState, useEffect } from "react";
|
||||
import { Radio, Switch } from "antd";
|
||||
import { Input } from "antd";
|
||||
import { Toast, Avatar, Popup } from "antd-mobile";
|
||||
import { ClockCircleOutlined, InfoCircleOutlined, DeleteOutlined, UserAddOutlined, ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import FriendSelection from "@/components/FriendSelection";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { GroupCreateFormData } from "../types";
|
||||
import style from "./BasicSettings.module.scss";
|
||||
|
||||
interface BasicSettingsProps {
|
||||
formData: GroupCreateFormData;
|
||||
onChange: (data: Partial<GroupCreateFormData>) => void;
|
||||
}
|
||||
|
||||
export interface BasicSettingsRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
({ formData, onChange }, ref) => {
|
||||
const [executorSelectionVisible, setExecutorSelectionVisible] = useState(false);
|
||||
const [groupAdminSelectionVisible, setGroupAdminSelectionVisible] = useState(false);
|
||||
const [fixedWechatIdsSelectionVisible, setFixedWechatIdsSelectionVisible] = useState(false);
|
||||
const [manualWechatIdInput, setManualWechatIdInput] = useState("");
|
||||
|
||||
|
||||
// 处理执行智能体选择(单选设备)
|
||||
const handleExecutorSelect = (devices: DeviceSelectionItem[]) => {
|
||||
if (devices.length > 0) {
|
||||
const selectedDevice = devices[0];
|
||||
// 自动设置群名称为执行智能体的名称(优先使用 nickname,其次 memo,最后 wechatId),加上"的群"后缀
|
||||
const executorName = selectedDevice.nickname || selectedDevice.memo || selectedDevice.wechatId || "";
|
||||
onChange({
|
||||
executor: selectedDevice,
|
||||
executorId: selectedDevice.id,
|
||||
groupName: executorName ? `${executorName}的群` : "", // 设置为"XXX的群"格式
|
||||
});
|
||||
} else {
|
||||
onChange({
|
||||
executor: undefined,
|
||||
executorId: undefined,
|
||||
});
|
||||
}
|
||||
setExecutorSelectionVisible(false);
|
||||
};
|
||||
|
||||
// 处理固定微信号选择(必须3个)
|
||||
const handleFixedWechatIdsSelect = (friends: FriendSelectionItem[]) => {
|
||||
// 检查总数是否超过3个(包括已添加的手动微信号)
|
||||
const currentManualCount = (formData.fixedWechatIdsOptions || []).filter(f => f.isManual).length;
|
||||
const newSelectedCount = friends.length;
|
||||
if (currentManualCount + newSelectedCount > 3) {
|
||||
Toast.show({ content: "固定微信号最多只能选择3个", position: "top" });
|
||||
return;
|
||||
}
|
||||
// 标记为选择的(非手动添加),确保所有从选择弹窗来的都标记为非手动
|
||||
const selectedFriends = friends.map(f => ({ ...f, isManual: false }));
|
||||
// 合并已添加的手动微信号和新的选择
|
||||
const manualFriends = (formData.fixedWechatIdsOptions || []).filter(f => f.isManual === true);
|
||||
onChange({
|
||||
fixedWechatIds: [...manualFriends, ...selectedFriends].map(f => f.id),
|
||||
fixedWechatIdsOptions: [...manualFriends, ...selectedFriends],
|
||||
});
|
||||
setFixedWechatIdsSelectionVisible(false);
|
||||
};
|
||||
|
||||
// 打开固定微信号选择弹窗前检查是否已选择执行智能体
|
||||
const handleOpenFixedWechatIdsSelection = () => {
|
||||
if (!formData.executorId) {
|
||||
Toast.show({ content: "请先选择执行智能体", position: "top" });
|
||||
return;
|
||||
}
|
||||
setFixedWechatIdsSelectionVisible(true);
|
||||
};
|
||||
|
||||
// 处理群管理员选择(单选)
|
||||
const handleGroupAdminSelect = (friends: FriendSelectionItem[]) => {
|
||||
if (friends.length > 0) {
|
||||
onChange({
|
||||
groupAdminWechatIdOption: friends[0],
|
||||
groupAdminWechatId: friends[0].id,
|
||||
});
|
||||
} else {
|
||||
onChange({
|
||||
groupAdminWechatIdOption: undefined,
|
||||
groupAdminWechatId: undefined,
|
||||
});
|
||||
}
|
||||
setGroupAdminSelectionVisible(false);
|
||||
};
|
||||
|
||||
// 手动添加微信号
|
||||
const handleAddManualWechatId = () => {
|
||||
if (!manualWechatIdInput.trim()) {
|
||||
Toast.show({ content: "请输入微信号", position: "top" });
|
||||
return;
|
||||
}
|
||||
const existingIds = formData.fixedWechatIdsOptions.map(f => f.wechatId.toLowerCase());
|
||||
if (existingIds.includes(manualWechatIdInput.trim().toLowerCase())) {
|
||||
Toast.show({ content: "该微信号已添加", position: "top" });
|
||||
return;
|
||||
}
|
||||
if (formData.fixedWechatIdsOptions.length >= 3) {
|
||||
Toast.show({ content: "固定微信号最多只能添加3个", position: "top" });
|
||||
return;
|
||||
}
|
||||
// 创建临时好友项,标记为手动添加
|
||||
const newFriend: FriendSelectionItem = {
|
||||
id: Date.now(), // 临时ID
|
||||
wechatId: manualWechatIdInput.trim(),
|
||||
nickname: manualWechatIdInput.trim(),
|
||||
avatar: "",
|
||||
isManual: true, // 标记为手动添加
|
||||
};
|
||||
onChange({
|
||||
fixedWechatIds: [...formData.fixedWechatIds, newFriend.id],
|
||||
fixedWechatIdsOptions: [...formData.fixedWechatIdsOptions, newFriend],
|
||||
});
|
||||
setManualWechatIdInput("");
|
||||
};
|
||||
|
||||
// 移除固定微信号
|
||||
const handleRemoveFixedWechatId = (id: number) => {
|
||||
onChange({
|
||||
fixedWechatIds: formData.fixedWechatIds.filter(fid => fid !== id),
|
||||
fixedWechatIdsOptions: formData.fixedWechatIdsOptions.filter(f => f.id !== id),
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
// 验证必填字段
|
||||
if (!formData.name?.trim()) {
|
||||
Toast.show({ content: "请输入计划名称", position: "top" });
|
||||
return false;
|
||||
}
|
||||
if (!formData.executorId) {
|
||||
Toast.show({ content: "请选择执行智能体", position: "top" });
|
||||
return false;
|
||||
}
|
||||
// 固定微信号不是必填的,移除验证
|
||||
if (!formData.groupName?.trim()) {
|
||||
Toast.show({ content: "请输入群名称", position: "top" });
|
||||
return false;
|
||||
}
|
||||
// 群名称长度验证(2-100个字符)
|
||||
const groupNameLength = formData.groupName.trim().length;
|
||||
if (groupNameLength < 2 || groupNameLength > 100) {
|
||||
Toast.show({ content: "群名称长度应在2-100个字符之间", position: "top" });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getValues: () => {
|
||||
return formData;
|
||||
},
|
||||
}));
|
||||
|
||||
// 区分已选择的微信号(从下拉选择)和已添加的微信号(手动输入)
|
||||
// 如果 isManual 未定义,默认为 false(即选择的)
|
||||
const selectedWechatIds = (formData.fixedWechatIdsOptions || []).filter(f => !f.isManual);
|
||||
const manualAddedWechatIds = (formData.fixedWechatIdsOptions || []).filter(f => f.isManual === true);
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
{/* 计划类型和计划名称 */}
|
||||
<div className={style.card}>
|
||||
<div>
|
||||
<label className={style.label}>计划类型</label>
|
||||
<Radio.Group
|
||||
value={formData.planType}
|
||||
onChange={e => onChange({ planType: e.target.value })}
|
||||
className={style.radioGroup}
|
||||
>
|
||||
<Radio value={0}>全局计划</Radio>
|
||||
<Radio value={1}>独立计划</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
<label className={style.label}>
|
||||
计划名称 <span className={style.labelRequired}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={style.input}
|
||||
value={formData.name}
|
||||
onChange={e => onChange({ name: e.target.value })}
|
||||
placeholder="请输入计划名称"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 执行智能体 */}
|
||||
<div className={style.card}>
|
||||
<label className={style.label}>
|
||||
<span className={style.labelRequired}>*</span>执行智能体
|
||||
</label>
|
||||
<div
|
||||
className={style.executorSelector}
|
||||
onClick={() => setExecutorSelectionVisible(true)}
|
||||
>
|
||||
{formData.executor ? (
|
||||
<div className={style.executorContent}>
|
||||
<div className={style.executorAvatar}>
|
||||
{formData.executor.avatar ? (
|
||||
<img src={formData.executor.avatar} alt={formData.executor.memo || formData.executor.wechatId} />
|
||||
) : (
|
||||
<span style={{ fontSize: "20px" }}>🤖</span>
|
||||
)}
|
||||
<div className={style.statusDot} style={{ background: formData.executor.status === "online" ? "#10b981" : "#94a3b8" }}></div>
|
||||
</div>
|
||||
<div className={style.executorInfo}>
|
||||
<div className={style.executorName}>
|
||||
{formData.executor.nickname || formData.executor.memo || formData.executor.wechatId}
|
||||
</div>
|
||||
<div className={style.executorId}>ID: {formData.executor.wechatId}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.executorContent}>
|
||||
<div className={style.executorAvatar}>
|
||||
<span style={{ fontSize: "20px", color: "#94a3b8" }}>🤖</span>
|
||||
</div>
|
||||
<div className={style.executorInfo}>
|
||||
<div className={style.executorName} style={{ color: "#cbd5e1" }}>
|
||||
请选择执行智能体
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<span className={style.executorExpand}>▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 固定微信号 */}
|
||||
<div className={style.card}>
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<label className={style.label}>
|
||||
固定微信号 <span className={style.labelRequired}>*</span>
|
||||
<InfoCircleOutlined className={style.infoIcon} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 点击选择框 */}
|
||||
<div
|
||||
className={`${style.wechatSelectInput} ${!formData.executorId ? style.disabled : ''}`}
|
||||
onClick={handleOpenFixedWechatIdsSelection}
|
||||
>
|
||||
<div className={style.selectInputWrapper}>
|
||||
<span className={style.selectInputPlaceholder}>
|
||||
{(selectedWechatIds.length + manualAddedWechatIds.length) > 0
|
||||
? `已选择 ${selectedWechatIds.length + manualAddedWechatIds.length} 个微信号`
|
||||
: '请选择微信号'}
|
||||
</span>
|
||||
<span className={style.selectInputArrow}>▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 已选择的微信号列表 */}
|
||||
{selectedWechatIds.length > 0 && (
|
||||
<div className={style.selectedList}>
|
||||
<p className={style.manualAddLabel}>已选择的微信号:</p>
|
||||
{selectedWechatIds.map(friend => (
|
||||
<div key={friend.id} className={style.selectedItem}>
|
||||
<div className={style.selectedItemContent}>
|
||||
<div className={style.selectedItemAvatar}>
|
||||
{friend.avatar ? (
|
||||
<img src={friend.avatar} alt={friend.nickname} />
|
||||
) : (
|
||||
<div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "14px", color: "#94a3b8" }}>
|
||||
{friend.nickname?.charAt(0) || "?"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={style.selectedItemInfo}>
|
||||
<div className={style.selectedItemName}>{friend.nickname || friend.wechatId}</div>
|
||||
<div className={style.selectedItemId}>微信ID: {friend.wechatId}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={style.deleteButton}
|
||||
onClick={() => handleRemoveFixedWechatId(friend.id)}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "18px" }} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 手动添加微信号 */}
|
||||
<div className={style.manualAdd}>
|
||||
<p className={style.manualAddLabel}>搜索不到?请输入微信号添加</p>
|
||||
<div className={style.manualAddInput}>
|
||||
<div className={style.manualAddInputWrapper}>
|
||||
<UserAddOutlined className={style.manualAddIcon} />
|
||||
<input
|
||||
type="text"
|
||||
className={style.manualAddInputField}
|
||||
value={manualWechatIdInput}
|
||||
onChange={e => setManualWechatIdInput(e.target.value)}
|
||||
placeholder="请输入微信号"
|
||||
/>
|
||||
</div>
|
||||
<button className={style.manualAddButton} onClick={handleAddManualWechatId}>
|
||||
<span>添</span>
|
||||
<span className={style.buttonText2}>加</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 已添加的微信号列表(手动输入的) */}
|
||||
{manualAddedWechatIds.length > 0 && (
|
||||
<div className={style.addedList}>
|
||||
<p className={style.manualAddLabel}>已添加的微信号:</p>
|
||||
{manualAddedWechatIds.map((friend, index) => (
|
||||
<div key={friend.id} className={style.addedItem}>
|
||||
<div className={style.addedItemContent}>
|
||||
<div className={style.addedItemNumber}>{index + 1}</div>
|
||||
<div className={style.addedItemInfo}>
|
||||
<div className={style.addedItemName}>{friend.nickname || friend.wechatId}</div>
|
||||
<div className={style.addedItemId}>微信ID: {friend.wechatId}</div>
|
||||
<div className={style.addedItemStatus}>{friend.wechatId} 已发起好友申请</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={style.deleteButton}
|
||||
onClick={() => handleRemoveFixedWechatId(friend.id)}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "18px" }} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<p className={style.addedCount}>已添加 {(selectedWechatIds.length + manualAddedWechatIds.length)}/3 个微信号</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 群管理员 */}
|
||||
<div className={style.card}>
|
||||
<div className={style.groupAdminHeader}>
|
||||
<div className={style.groupAdminLabelWrapper}>
|
||||
<label className={style.label}>群管理员</label>
|
||||
<InfoCircleOutlined className={style.infoIcon} />
|
||||
</div>
|
||||
<Switch
|
||||
checked={formData.groupAdminEnabled}
|
||||
onChange={checked => onChange({ groupAdminEnabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
{formData.groupAdminEnabled && (
|
||||
<div
|
||||
className={`${style.wechatSelectInput} ${(selectedWechatIds.length + manualAddedWechatIds.length) === 0 ? style.disabled : ''}`}
|
||||
onClick={() => {
|
||||
if ((selectedWechatIds.length + manualAddedWechatIds.length) === 0) {
|
||||
Toast.show({ content: "请先选择固定微信号", position: "top" });
|
||||
return;
|
||||
}
|
||||
setGroupAdminSelectionVisible(true);
|
||||
}}
|
||||
>
|
||||
<div className={style.selectInputWrapper}>
|
||||
<span className={style.selectInputPlaceholder}>
|
||||
{formData.groupAdminWechatIdOption
|
||||
? (formData.groupAdminWechatIdOption.nickname || formData.groupAdminWechatIdOption.wechatId)
|
||||
: '请选择群管理员微信号'}
|
||||
</span>
|
||||
<span className={style.selectInputArrow}>▼</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<p className={style.groupAdminHint}>开启后,所选微信号将自动成为群管理员。</p>
|
||||
</div>
|
||||
|
||||
{/* 群名称 */}
|
||||
<div className={style.card}>
|
||||
<label className={style.label}>群名称</label>
|
||||
<input
|
||||
type="text"
|
||||
className={style.input}
|
||||
value={formData.groupName}
|
||||
onChange={e => onChange({ groupName: e.target.value })}
|
||||
placeholder="请输入群名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 群人数配置 */}
|
||||
<div className={style.card}>
|
||||
<label className={style.label}>群人数配置</label>
|
||||
|
||||
<div className={style.groupSizeConfig}>
|
||||
{/* 每日最大建群数 */}
|
||||
<div>
|
||||
<label className={style.groupSizeLabel}>每日最大建群数</label>
|
||||
<input
|
||||
type="number"
|
||||
className={style.input}
|
||||
value={formData.maxGroupsPerDay || ""}
|
||||
onChange={e => onChange({ maxGroupsPerDay: Number(e.target.value) || 0 })}
|
||||
placeholder="请输入数量"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 群组最小人数和最大人数 */}
|
||||
<div className={style.groupSizeRow}>
|
||||
<div className={style.groupSizeItem}>
|
||||
<label className={style.groupSizeLabel}>群组最小人数</label>
|
||||
<input
|
||||
type="number"
|
||||
className={style.input}
|
||||
value={formData.groupSizeMin || ""}
|
||||
onChange={e => {
|
||||
const value = Number(e.target.value) || 0;
|
||||
onChange({ groupSizeMin: value < 3 ? 3 : value });
|
||||
}}
|
||||
placeholder="如: 3"
|
||||
min={3}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.groupSizeItem}>
|
||||
<label className={style.groupSizeLabel}>群组最大人数</label>
|
||||
<input
|
||||
type="number"
|
||||
className={style.input}
|
||||
value={formData.groupSizeMax || ""}
|
||||
onChange={e => {
|
||||
const value = Number(e.target.value) || 0;
|
||||
onChange({ groupSizeMax: value > 38 ? 38 : value });
|
||||
}}
|
||||
placeholder="如: 40"
|
||||
max={38}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 执行时间 */}
|
||||
<div className={style.card}>
|
||||
<label className={style.label}>执行时间</label>
|
||||
<div className={style.timeRangeContainer}>
|
||||
<div className={style.timeInputWrapper}>
|
||||
<input
|
||||
type="time"
|
||||
className={style.timeInput}
|
||||
value={formData.executeTime || "09:00"}
|
||||
onChange={e => onChange({ executeTime: e.target.value || "09:00" })}
|
||||
/>
|
||||
<ClockCircleOutlined className={style.timeIcon} />
|
||||
</div>
|
||||
<span className={style.timeSeparator}>-</span>
|
||||
<div className={style.timeInputWrapper}>
|
||||
<input
|
||||
type="time"
|
||||
className={style.timeInput}
|
||||
value={formData.executeEndTime || "18:00"}
|
||||
onChange={e => onChange({ executeEndTime: e.target.value || "18:00" })}
|
||||
/>
|
||||
<ClockCircleOutlined className={style.timeIcon} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{(selectedWechatIds.length + manualAddedWechatIds.length) !== 3 && (
|
||||
<div className={style.errorTip}>
|
||||
<ExclamationCircleOutlined className={style.errorIcon} />
|
||||
<span className={style.errorText}>固定微信号必须选择3个</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 隐藏的选择组件 */}
|
||||
<div style={{ display: "none" }}>
|
||||
<DeviceSelection
|
||||
selectedOptions={formData.executor ? [formData.executor] : []}
|
||||
onSelect={handleExecutorSelect}
|
||||
placeholder="选择执行智能体"
|
||||
showInput={false}
|
||||
showSelectedList={false}
|
||||
singleSelect={true}
|
||||
mode="dialog"
|
||||
open={executorSelectionVisible}
|
||||
onOpenChange={setExecutorSelectionVisible}
|
||||
/>
|
||||
<FriendSelection
|
||||
visible={fixedWechatIdsSelectionVisible}
|
||||
onVisibleChange={setFixedWechatIdsSelectionVisible}
|
||||
selectedOptions={selectedWechatIds}
|
||||
onSelect={handleFixedWechatIdsSelect}
|
||||
placeholder="选择微信号"
|
||||
showInput={false}
|
||||
showSelectedList={false}
|
||||
deviceIds={formData.executorId ? [formData.executorId] : []}
|
||||
enableDeviceFilter={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 群管理员选择弹窗 - 只显示固定微信号列表 */}
|
||||
<Popup
|
||||
visible={groupAdminSelectionVisible}
|
||||
onMaskClick={() => setGroupAdminSelectionVisible(false)}
|
||||
position="bottom"
|
||||
bodyStyle={{ height: "60vh" }}
|
||||
>
|
||||
<div style={{ padding: "16px" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
|
||||
<h3 style={{ margin: 0, fontSize: "16px", fontWeight: 600 }}>选择群管理员微信号</h3>
|
||||
<button
|
||||
onClick={() => setGroupAdminSelectionVisible(false)}
|
||||
style={{ background: "none", border: "none", fontSize: "16px", color: "#3b82f6", cursor: "pointer" }}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ maxHeight: "50vh", overflowY: "auto" }}>
|
||||
{(selectedWechatIds.length + manualAddedWechatIds.length) === 0 ? (
|
||||
<div style={{ textAlign: "center", padding: "40px", color: "#94a3b8" }}>
|
||||
暂无固定微信号可选
|
||||
</div>
|
||||
) : (
|
||||
[...selectedWechatIds, ...manualAddedWechatIds].map(friend => {
|
||||
const isSelected = formData.groupAdminWechatIdOption?.id === friend.id;
|
||||
return (
|
||||
<div
|
||||
key={friend.id}
|
||||
onClick={() => {
|
||||
onChange({
|
||||
groupAdminWechatIdOption: friend,
|
||||
groupAdminWechatId: friend.id,
|
||||
});
|
||||
setGroupAdminSelectionVisible(false);
|
||||
}}
|
||||
style={{
|
||||
padding: "12px",
|
||||
marginBottom: "8px",
|
||||
borderRadius: "8px",
|
||||
border: `1px solid ${isSelected ? "#3b82f6" : "#e2e8f0"}`,
|
||||
background: isSelected ? "#eff6ff" : "#fff",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isSelected) {
|
||||
e.currentTarget.style.borderColor = "#3b82f6";
|
||||
e.currentTarget.style.background = "#f8fafc";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isSelected) {
|
||||
e.currentTarget.style.borderColor = "#e2e8f0";
|
||||
e.currentTarget.style.background = "#fff";
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||
<div style={{
|
||||
width: "36px",
|
||||
height: "36px",
|
||||
borderRadius: "50%",
|
||||
background: "#e2e8f0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{friend.avatar ? (
|
||||
<img src={friend.avatar} alt={friend.nickname} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
|
||||
) : (
|
||||
<span style={{ fontSize: "14px", color: "#94a3b8" }}>
|
||||
{friend.nickname?.charAt(0) || friend.wechatId?.charAt(0) || "?"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: "14px", fontWeight: 500, color: "#1e293b", marginBottom: "2px" }}>
|
||||
{friend.nickname || friend.wechatId}
|
||||
</div>
|
||||
<div style={{ fontSize: "12px", color: "#64748b" }}>
|
||||
微信ID: {friend.wechatId}
|
||||
</div>
|
||||
</div>
|
||||
{isSelected && (
|
||||
<span style={{ color: "#3b82f6", fontSize: "16px" }}>✓</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BasicSettings.displayName = "BasicSettings";
|
||||
|
||||
export default BasicSettings;
|
||||
@@ -0,0 +1,197 @@
|
||||
.container {
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.deviceSelectorWrapper {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.deviceSelector {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #f8fafc;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
.selectedDevicesInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selectedCountText {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.deviceNames {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
flex: 1;
|
||||
color: #cbd5e1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.expandIcon {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.selectedDevicesGrid {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.selectedDeviceCard {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid #3b82f6;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.deviceCardHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.deviceCardIcon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.deviceIconOnline {
|
||||
background: #e0f2fe;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.deviceIconOffline {
|
||||
background: #f3f4f6;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.deviceCardStatusBadge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 9999px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.statusOnline {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.statusOffline {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.deviceCardName {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0 0 8px 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.deviceCardInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.deviceCardPhone {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.deviceCardDeleteButton {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.deviceCardDeleteIcon {
|
||||
color: #ef4444;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import React, { useState, useRef, useImperativeHandle, forwardRef } from "react";
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import style from "./DeviceSelectionStep.module.scss";
|
||||
|
||||
export interface DeviceSelectionStepRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
interface DeviceSelectionStepProps {
|
||||
selectedDevices?: DeviceSelectionItem[];
|
||||
selectedDeviceIds?: number[];
|
||||
onSelect: (devices: DeviceSelectionItem[]) => void;
|
||||
}
|
||||
|
||||
const DeviceSelectionStep = forwardRef<DeviceSelectionStepRef, DeviceSelectionStepProps>(
|
||||
({ selectedDevices = [], selectedDeviceIds = [], onSelect }, ref) => {
|
||||
const [deviceSelectionVisible, setDeviceSelectionVisible] = useState(false);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
if (selectedDeviceIds.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getValues: () => {
|
||||
return { selectedDevices, selectedDeviceIds };
|
||||
},
|
||||
}));
|
||||
|
||||
const handleDeviceSelect = (devices: DeviceSelectionItem[]) => {
|
||||
onSelect(devices);
|
||||
setDeviceSelectionVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<div className={style.header}>
|
||||
<h2 className={style.title}>选择设备</h2>
|
||||
</div>
|
||||
|
||||
<div className={style.deviceSelectorWrapper}>
|
||||
<div
|
||||
className={style.deviceSelector}
|
||||
onClick={() => setDeviceSelectionVisible(true)}
|
||||
>
|
||||
{selectedDevices.length > 0 ? (
|
||||
<div className={style.selectedDevicesInfo}>
|
||||
<span className={style.selectedCountText}>
|
||||
已选择 {selectedDevices.length} 个设备
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.placeholder}>
|
||||
<span>请选择执行设备</span>
|
||||
</div>
|
||||
)}
|
||||
<span className={style.expandIcon}>▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 已选设备列表 */}
|
||||
{selectedDevices.length > 0 && (
|
||||
<div className={style.selectedDevicesGrid}>
|
||||
{selectedDevices.map((device) => (
|
||||
<div key={device.id} className={style.selectedDeviceCard}>
|
||||
<div className={style.deviceCardHeader}>
|
||||
<div className={`${style.deviceCardIcon} ${device.status === "online" ? style.deviceIconOnline : style.deviceIconOffline}`}>
|
||||
{device.avatar ? (
|
||||
<img src={device.avatar} alt={device.memo || device.wechatId} />
|
||||
) : (
|
||||
<span>📱</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={`${style.deviceCardStatusBadge} ${device.status === "online" ? style.statusOnline : style.statusOffline}`}>
|
||||
{device.status === "online" ? "在线" : "离线"}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className={style.deviceCardName}>{device.memo || device.wechatId}</h3>
|
||||
<div className={style.deviceCardInfo}>
|
||||
<p className={style.deviceCardPhone}>{device.wechatId}</p>
|
||||
</div>
|
||||
<div
|
||||
className={style.deviceCardDeleteButton}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const newSelectedDevices = selectedDevices.filter(d => d.id !== device.id);
|
||||
onSelect(newSelectedDevices);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined className={style.deviceCardDeleteIcon} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 设备选择弹窗 */}
|
||||
<div style={{ display: "none" }}>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
placeholder="选择设备"
|
||||
showInput={false}
|
||||
showSelectedList={false}
|
||||
singleSelect={false}
|
||||
mode="dialog"
|
||||
open={deviceSelectionVisible}
|
||||
onOpenChange={setDeviceSelectionVisible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DeviceSelectionStep.displayName = "DeviceSelectionStep";
|
||||
|
||||
export default DeviceSelectionStep;
|
||||
@@ -0,0 +1,233 @@
|
||||
.container {
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.headerTop {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.manageLink {
|
||||
font-size: 14px;
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.linkIcon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.infoBox {
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
color: #3b82f6;
|
||||
font-size: 20px;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.infoText {
|
||||
font-size: 14px;
|
||||
color: #1e40af;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.poolSelectorWrapper {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.poolSelector {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
.selectedPoolsInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selectedCountText {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
flex: 1;
|
||||
color: #cbd5e1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.expandIcon {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.selectedPoolsList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.selectedPoolCard {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 4px 20px -2px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.poolCardContent {
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.poolCardHeader {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.poolCardName {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.poolCardDescription {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.poolCardStats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.poolCardUsers {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #3b82f6;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.usersIcon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.usersCount {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.poolCardDivider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background: #d1d5db;
|
||||
}
|
||||
|
||||
.poolCardTime {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.poolCardTags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.poolTag {
|
||||
padding: 4px 10px;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #3b82f6;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
|
||||
.poolCardDeleteButton {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.poolCardDeleteIcon {
|
||||
color: #ef4444;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import React, { useState, useRef, useImperativeHandle, forwardRef } from "react";
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import style from "./PoolSelectionStep.module.scss";
|
||||
|
||||
export interface PoolSelectionStepRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
interface PoolSelectionStepProps {
|
||||
selectedPools?: PoolSelectionItem[];
|
||||
poolGroups?: string[];
|
||||
onSelect: (pools: PoolSelectionItem[], poolGroups: string[]) => void;
|
||||
}
|
||||
|
||||
const PoolSelectionStep = forwardRef<PoolSelectionStepRef, PoolSelectionStepProps>(
|
||||
({ selectedPools = [], poolGroups = [], onSelect }, ref) => {
|
||||
const [poolSelectionVisible, setPoolSelectionVisible] = useState(false);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
if (selectedPools.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getValues: () => {
|
||||
return { selectedPools, poolGroups };
|
||||
},
|
||||
}));
|
||||
|
||||
const handlePoolSelect = (pools: PoolSelectionItem[]) => {
|
||||
const poolGroupIds = pools.map(p => p.id);
|
||||
onSelect(pools, poolGroupIds);
|
||||
setPoolSelectionVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<div className={style.header}>
|
||||
<div className={style.headerTop}>
|
||||
<h2 className={style.title}>选择流量池</h2>
|
||||
<a
|
||||
className={style.manageLink}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
// TODO: 导航到流量池管理页面
|
||||
}}
|
||||
>
|
||||
前往流量池管理
|
||||
<span className={style.linkIcon}>↗</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.infoBox}>
|
||||
<span className={style.infoIcon}>ℹ</span>
|
||||
<p className={style.infoText}>
|
||||
选择流量池后,系统将自动筛选出该流量池中的用户,以确定自动建群所针对的目标群体。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={style.poolSelectorWrapper}>
|
||||
<div
|
||||
className={style.poolSelector}
|
||||
onClick={() => setPoolSelectionVisible(true)}
|
||||
>
|
||||
{selectedPools.length > 0 ? (
|
||||
<div className={style.selectedPoolsInfo}>
|
||||
<span className={style.selectedCountText}>
|
||||
已选择 {selectedPools.length} 个流量池
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.placeholder}>
|
||||
<span>请选择流量池</span>
|
||||
</div>
|
||||
)}
|
||||
<span className={style.expandIcon}>▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 已选流量池列表 */}
|
||||
{selectedPools.length > 0 && (
|
||||
<div className={style.selectedPoolsList}>
|
||||
{selectedPools.map((pool) => (
|
||||
<div key={pool.id} className={style.selectedPoolCard}>
|
||||
<div className={style.poolCardContent}>
|
||||
<div className={style.poolCardHeader}>
|
||||
<h3 className={style.poolCardName}>{pool.name}</h3>
|
||||
</div>
|
||||
{pool.description && (
|
||||
<p className={style.poolCardDescription}>{pool.description}</p>
|
||||
)}
|
||||
<div className={style.poolCardStats}>
|
||||
<div className={style.poolCardUsers}>
|
||||
<span className={style.usersIcon}>👥</span>
|
||||
<span className={style.usersCount}>{pool.num || 0} 人</span>
|
||||
</div>
|
||||
{pool.createTime && (
|
||||
<>
|
||||
<div className={style.poolCardDivider}></div>
|
||||
<span className={style.poolCardTime}>
|
||||
更新于 {pool.createTime}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{pool.tags && pool.tags.length > 0 && (
|
||||
<div className={style.poolCardTags}>
|
||||
{pool.tags.map((tag: any, index: number) => (
|
||||
<span key={index} className={style.poolTag}>
|
||||
{typeof tag === 'string' ? tag : tag.name || tag.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={style.poolCardDeleteButton}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const newSelectedPools = selectedPools.filter(p => p.id !== pool.id);
|
||||
const newPoolGroups = newSelectedPools.map(p => p.id);
|
||||
onSelect(newSelectedPools, newPoolGroups);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined className={style.poolCardDeleteIcon} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 流量池选择弹窗 */}
|
||||
<PoolSelection
|
||||
selectedOptions={selectedPools}
|
||||
onSelect={handlePoolSelect}
|
||||
placeholder="选择流量池"
|
||||
showInput={false}
|
||||
showSelectedList={false}
|
||||
visible={poolSelectionVisible}
|
||||
onVisibleChange={setPoolSelectionVisible}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
PoolSelectionStep.displayName = "PoolSelectionStep";
|
||||
|
||||
export default PoolSelectionStep;
|
||||
284
Cunkebao/src/pages/mobile/workspace/group-create/form/index.tsx
Normal file
284
Cunkebao/src/pages/mobile/workspace/group-create/form/index.tsx
Normal file
@@ -0,0 +1,284 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Toast } from "antd-mobile";
|
||||
import { Button } from "antd";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { createGroupCreate, updateGroupCreate, getGroupCreateDetail } from "./api";
|
||||
import { GroupCreateFormData } from "./types";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import DeviceSelectionStep, { DeviceSelectionStepRef } from "./components/DeviceSelectionStep";
|
||||
import PoolSelectionStep, { PoolSelectionStepRef } from "./components/PoolSelectionStep";
|
||||
import NavCommon from "@/components/NavCommon/index";
|
||||
|
||||
const steps = [
|
||||
{ id: 1, title: "1", subtitle: "群设置" },
|
||||
{ id: 2, title: "2", subtitle: "设备选择" },
|
||||
{ id: 3, title: "3", subtitle: "流量池选择" },
|
||||
];
|
||||
|
||||
const defaultForm: GroupCreateFormData = {
|
||||
planType: 1, // 默认独立计划
|
||||
name: "新建群计划",
|
||||
executorId: undefined,
|
||||
executor: undefined,
|
||||
selectedDevices: [],
|
||||
selectedDeviceIds: [],
|
||||
fixedWechatIds: [],
|
||||
fixedWechatIdsOptions: [],
|
||||
groupAdminEnabled: false,
|
||||
groupAdminWechatId: undefined,
|
||||
groupAdminWechatIdOption: undefined,
|
||||
groupName: "卡若001",
|
||||
groupMethod: 0, // 默认所有好友自动分组
|
||||
maxGroupsPerDay: 10,
|
||||
groupSizeMin: 3,
|
||||
groupSizeMax: 38,
|
||||
executeType: 1, // 默认定时执行
|
||||
executeTime: "09:00", // 默认开始时间
|
||||
executeEndTime: "18:00", // 默认结束时间
|
||||
poolGroups: [],
|
||||
poolGroupsOptions: [],
|
||||
status: 1, // 默认启用
|
||||
};
|
||||
|
||||
const GroupCreateForm: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const isEdit = Boolean(id);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoaded, setDataLoaded] = useState(!isEdit);
|
||||
const [formData, setFormData] = useState<GroupCreateFormData>(defaultForm);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const deviceSelectionStepRef = useRef<DeviceSelectionStepRef>(null);
|
||||
const poolSelectionStepRef = useRef<PoolSelectionStepRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
// 获取详情并回填表单
|
||||
getGroupCreateDetail(id)
|
||||
.then(res => {
|
||||
const updatedForm: GroupCreateFormData = {
|
||||
...defaultForm,
|
||||
id: res.id,
|
||||
planType: res.planType ?? 1,
|
||||
name: res.name ?? "",
|
||||
executorId: res.executorId,
|
||||
executor: res.executor,
|
||||
selectedDevices: res.selectedDevices || [],
|
||||
selectedDeviceIds: res.selectedDeviceIds || [],
|
||||
fixedWechatIds: res.fixedWechatIds || [],
|
||||
fixedWechatIdsOptions: res.fixedWechatIdsOptions || [],
|
||||
groupAdminEnabled: res.groupAdminEnabled ?? false,
|
||||
groupAdminWechatId: res.groupAdminWechatId,
|
||||
groupAdminWechatIdOption: res.groupAdminWechatIdOption,
|
||||
groupName: res.groupName ?? "",
|
||||
groupMethod: res.groupMethod ?? 0,
|
||||
maxGroupsPerDay: res.maxGroupsPerDay ?? 10,
|
||||
groupSizeMin: res.groupSizeMin ?? 3,
|
||||
groupSizeMax: res.groupSizeMax ?? 38,
|
||||
executeType: res.executeType ?? 1,
|
||||
executeTime: res.executeTime ?? "09:00",
|
||||
executeEndTime: res.executeEndTime ?? "18:00",
|
||||
poolGroups: res.poolGroups || [],
|
||||
poolGroupsOptions: res.poolGroupsOptions || [],
|
||||
status: res.status ?? 1,
|
||||
};
|
||||
setFormData(updatedForm);
|
||||
setDataLoaded(true);
|
||||
})
|
||||
.catch(err => {
|
||||
Toast.show({ content: err.message || "获取详情失败" });
|
||||
setDataLoaded(true);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const handleFormDataChange = (values: Partial<GroupCreateFormData>) => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
if (currentStep === 1) {
|
||||
// 验证第一步
|
||||
const isValid = (await basicSettingsRef.current?.validate()) || false;
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
setCurrentStep(2);
|
||||
// 切换到下一步时,滚动到顶部
|
||||
setTimeout(() => {
|
||||
const mainElement = document.querySelector('main');
|
||||
if (mainElement) {
|
||||
mainElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}, 100);
|
||||
} else if (currentStep === 2) {
|
||||
// 验证第二步
|
||||
const isValid = (await deviceSelectionStepRef.current?.validate()) || false;
|
||||
if (!isValid) {
|
||||
Toast.show({ content: "请至少选择一个执行设备", position: "top" });
|
||||
return;
|
||||
}
|
||||
setCurrentStep(3);
|
||||
// 切换到下一步时,滚动到顶部
|
||||
setTimeout(() => {
|
||||
const mainElement = document.querySelector('main');
|
||||
if (mainElement) {
|
||||
mainElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}, 100);
|
||||
} else if (currentStep === 3) {
|
||||
// 验证第三步并保存
|
||||
await handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// 验证第三步
|
||||
const isValid = (await poolSelectionStepRef.current?.validate()) || false;
|
||||
if (!isValid) {
|
||||
Toast.show({ content: "请至少选择一个流量池", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
...formData,
|
||||
type: 4, // 自动建群任务类型(保持与旧版一致)
|
||||
};
|
||||
|
||||
if (isEdit) {
|
||||
await updateGroupCreate(submitData);
|
||||
Toast.show({ content: "编辑成功" });
|
||||
} else {
|
||||
await createGroupCreate(submitData);
|
||||
Toast.show({ content: "创建成功" });
|
||||
}
|
||||
// 注意:需要导航到列表页面,但目前列表页面还未创建
|
||||
// navigate("/workspace/group-create");
|
||||
navigate("/workspace");
|
||||
} catch (e: any) {
|
||||
Toast.show({ content: e?.message || "提交失败" });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCurrentStep = () => {
|
||||
// 编辑模式下,等待数据加载完成后再渲染
|
||||
if (isEdit && !dataLoaded) {
|
||||
return (
|
||||
<div style={{ textAlign: "center", padding: "50px" }}>加载中...</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<BasicSettings
|
||||
ref={basicSettingsRef}
|
||||
formData={formData}
|
||||
onChange={handleFormDataChange}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<DeviceSelectionStep
|
||||
ref={deviceSelectionStepRef}
|
||||
selectedDevices={formData.selectedDevices || []}
|
||||
selectedDeviceIds={formData.selectedDeviceIds || []}
|
||||
onSelect={(devices) => {
|
||||
const deviceIds = devices.map(d => d.id);
|
||||
handleFormDataChange({
|
||||
selectedDevices: devices,
|
||||
selectedDeviceIds: deviceIds,
|
||||
// 如果只有一个设备,也设置 executor 和 executorId 用于兼容
|
||||
executor: devices.length === 1 ? devices[0] : undefined,
|
||||
executorId: devices.length === 1 ? devices[0].id : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<PoolSelectionStep
|
||||
ref={poolSelectionStepRef}
|
||||
selectedPools={formData.poolGroupsOptions || []}
|
||||
poolGroups={formData.poolGroups || []}
|
||||
onSelect={(pools, poolGroupIds) => {
|
||||
handleFormDataChange({
|
||||
poolGroupsOptions: pools,
|
||||
poolGroups: poolGroupIds,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "12px", padding: "16px" }}>
|
||||
{currentStep > 1 && (
|
||||
<Button
|
||||
size="large"
|
||||
onClick={() => {
|
||||
setCurrentStep(currentStep - 1);
|
||||
// 切换到上一步时,滚动到顶部
|
||||
setTimeout(() => {
|
||||
const mainElement = document.querySelector('main');
|
||||
if (mainElement) {
|
||||
mainElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}, 100);
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleNext}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{currentStep === 3 ? "完成" : "下一步"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<>
|
||||
<NavCommon
|
||||
title={isEdit ? "编辑自动建群" : "新建自动建群"}
|
||||
backFn={() => navigate(-1)}
|
||||
/>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
</>
|
||||
}
|
||||
footer={renderFooter()}
|
||||
>
|
||||
<div>{renderCurrentStep()}</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupCreateForm;
|
||||
103
Cunkebao/src/pages/mobile/workspace/group-create/form/types.ts
Normal file
103
Cunkebao/src/pages/mobile/workspace/group-create/form/types.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
// 自动建群表单数据类型定义(新版)
|
||||
export interface GroupCreateFormData {
|
||||
id?: string; // 任务ID
|
||||
planType: number; // 计划类型:0-全局计划,1-独立计划
|
||||
name: string; // 计划名称
|
||||
executor?: DeviceSelectionItem; // 执行智能体(执行者)- 单个设备(保留用于兼容)
|
||||
executorId?: number; // 执行智能体ID(设备ID)(保留用于兼容)
|
||||
selectedDevices?: DeviceSelectionItem[]; // 选中的设备列表
|
||||
selectedDeviceIds?: number[]; // 选中的设备ID列表
|
||||
fixedWechatIds: number[]; // 固定微信号ID列表(必须3个)
|
||||
fixedWechatIdsOptions: FriendSelectionItem[]; // 固定微信号选项
|
||||
groupAdminEnabled: boolean; // 群管理员开关
|
||||
groupAdminWechatId?: number; // 群管理员微信号ID
|
||||
groupAdminWechatIdOption?: FriendSelectionItem; // 群管理员微信号选项
|
||||
groupName: string; // 群名称
|
||||
groupMethod: number; // 分组方式:0-所有好友自动分组,1-指定群数量
|
||||
maxGroupsPerDay: number; // 每日最大建群数(当groupMethod为1时使用)
|
||||
groupSizeMin: number; // 群组最小人数
|
||||
groupSizeMax: number; // 群组最大人数
|
||||
executeType: number; // 执行类型:0-立即执行,1-定时执行
|
||||
executeTime?: string; // 执行开始时间(HH:mm),默认 09:00
|
||||
executeEndTime?: string; // 执行结束时间(HH:mm),默认 18:00
|
||||
poolGroups?: string[]; // 流量池ID列表
|
||||
poolGroupsOptions?: PoolSelectionItem[]; // 流量池选项列表
|
||||
status: number; // 是否启用 (1: 启用, 0: 禁用)
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 步骤定义
|
||||
export interface StepItem {
|
||||
id: number;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const formValidationRules = {
|
||||
name: [
|
||||
{ required: true, message: "请输入计划名称" },
|
||||
{ min: 2, max: 50, message: "计划名称长度应在2-50个字符之间" },
|
||||
],
|
||||
executorId: [{ required: true, message: "请选择执行智能体" }],
|
||||
fixedWechatIds: [
|
||||
{ required: true, message: "请选择固定微信号" },
|
||||
{
|
||||
validator: (_: any, value: number[]) => {
|
||||
if (!value || value.length !== 3) {
|
||||
return Promise.reject(new Error("固定微信号必须选择3个"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
groupName: [
|
||||
{ required: true, message: "请输入群名称" },
|
||||
{ min: 2, max: 100, message: "群名称长度应在2-100个字符之间" },
|
||||
],
|
||||
maxGroupsPerDay: [
|
||||
{ required: true, message: "请输入每日最大建群数" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
message: "每日最大建群数应在1-100之间",
|
||||
},
|
||||
],
|
||||
groupSizeMin: [
|
||||
{ required: true, message: "请输入群组最小人数" },
|
||||
{ type: "number", min: 1, max: 500, message: "群组最小人数应在1-500之间" },
|
||||
],
|
||||
groupSizeMax: [
|
||||
{ required: true, message: "请输入群组最大人数" },
|
||||
{ type: "number", min: 1, max: 500, message: "群组最大人数应在1-500之间" },
|
||||
],
|
||||
executeDate: [
|
||||
{
|
||||
validator: (_: any, value: string, callback: any) => {
|
||||
// 如果执行类型是定时执行,则必填
|
||||
const executeType = callback?.executeType ?? 1;
|
||||
if (executeType === 1 && !value) {
|
||||
return Promise.reject(new Error("请选择执行日期"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
executeTime: [
|
||||
{
|
||||
validator: (_: any, value: string, callback: any) => {
|
||||
// 如果执行类型是定时执行,则必填
|
||||
const executeType = callback?.executeType ?? 1;
|
||||
if (executeType === 1 && !value) {
|
||||
return Promise.reject(new Error("请选择执行时间"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
92
Cunkebao/src/pages/mobile/workspace/group-create/新增字段说明.md
Normal file
92
Cunkebao/src/pages/mobile/workspace/group-create/新增字段说明.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 自动建群(group-create)新增字段说明
|
||||
|
||||
## 新增字段列表
|
||||
|
||||
相对于旧的 auto-group 模块,新版本(group-create)新增了以下字段:
|
||||
|
||||
### 1. planType (计划类型)
|
||||
- **类型**: number
|
||||
- **取值**: 0-全局计划, 1-独立计划
|
||||
- **默认值**: 1 (独立计划)
|
||||
- **说明**: 计划类型,用于区分全局计划和独立计划
|
||||
|
||||
### 2. executor / executorId (执行智能体)
|
||||
- **类型**: FriendSelectionItem / number
|
||||
- **说明**: 执行智能体(执行者),单个微信号选择
|
||||
- **必填**: 是
|
||||
- **替换**: 原 `devices` 字段(原为设备选择,现改为微信号选择)
|
||||
|
||||
### 3. fixedWechatIds / fixedWechatIdsOptions (固定微信号)
|
||||
- **类型**: number[] / FriendSelectionItem[]
|
||||
- **说明**: 固定微信号,必须选择3个
|
||||
- **必填**: 是
|
||||
- **新增**: 完全新增的字段,旧版本没有对应字段
|
||||
- **功能**: 支持搜索选择,也支持手动输入微信号添加
|
||||
|
||||
### 4. groupAdminEnabled (群管理员开关)
|
||||
- **类型**: boolean
|
||||
- **默认值**: false
|
||||
- **说明**: 是否启用群管理员功能
|
||||
- **新增**: 完全新增的字段
|
||||
|
||||
### 5. groupAdminWechatId / groupAdminWechatIdOption (群管理员微信号)
|
||||
- **类型**: number / FriendSelectionItem
|
||||
- **说明**: 群管理员微信号(当 groupAdminEnabled 为 true 时选择)
|
||||
- **必填**: 否(仅在 groupAdminEnabled 为 true 时选择)
|
||||
- **新增**: 完全新增的字段
|
||||
- **替换**: 原 `admins` 字段(原为多个管理员,现改为单个可选的管理员)
|
||||
|
||||
### 6. groupName (群名称)
|
||||
- **类型**: string
|
||||
- **说明**: 群名称
|
||||
- **必填**: 是
|
||||
- **替换**: 原 `groupNameTemplate` (群名称模板)
|
||||
- **变更**: 从模板改为直接输入群名称
|
||||
|
||||
### 7. executeType (执行类型)
|
||||
- **类型**: number
|
||||
- **取值**: 0-立即执行, 1-定时执行
|
||||
- **默认值**: 1 (定时执行)
|
||||
- **说明**: 执行类型
|
||||
- **新增**: 完全新增的字段
|
||||
|
||||
### 8. executeDate (执行日期)
|
||||
- **类型**: string (YYYY-MM-DD)
|
||||
- **说明**: 执行日期,仅在 executeType === 1 (定时执行) 时必填
|
||||
- **新增**: 完全新增的字段
|
||||
|
||||
### 9. executeTime (执行时间)
|
||||
- **类型**: string (HH:mm)
|
||||
- **说明**: 执行时间,仅在 executeType === 1 (定时执行) 时必填
|
||||
- **新增**: 完全新增的字段
|
||||
|
||||
## 已删除的字段
|
||||
|
||||
以下字段在新版本中不再使用:
|
||||
|
||||
1. **devices / devicesOptions** - 被 executor / executorId 替换(从设备选择改为微信号选择)
|
||||
2. **admins / adminsOptions** - 被 groupAdminWechatId / groupAdminWechatIdOption 替换(从多个管理员改为单个可选管理员)
|
||||
3. **poolGroups / poolGroupsOptions** - 流量池选择,新版本不再需要
|
||||
4. **startTime / endTime** - 允许建群的时间段,新版本改为 executeDate + executeTime
|
||||
5. **groupNameTemplate** - 被 groupName 替换(从模板改为直接输入)
|
||||
6. **groupDescription** - 群描述,新版本不再需要
|
||||
|
||||
## 保留的字段
|
||||
|
||||
以下字段在新版本中继续使用:
|
||||
|
||||
1. **id** - 任务ID
|
||||
2. **name** - 计划名称(保持原有逻辑)
|
||||
3. **maxGroupsPerDay** - 每日最大建群数(保持原有逻辑)
|
||||
4. **groupSizeMin** - 群组最小人数(保持原有逻辑)
|
||||
5. **groupSizeMax** - 群组最大人数(保持原有逻辑)
|
||||
6. **status** - 是否启用(保持原有逻辑)
|
||||
7. **type** - 任务类型(固定为4,与旧版保持一致)
|
||||
|
||||
## 接口说明
|
||||
|
||||
接口路径保持与旧版一致:
|
||||
- 创建: POST /v1/workbench/create
|
||||
- 更新: POST /v1/workbench/update
|
||||
- 详情: GET /v1/workbench/detail
|
||||
- 列表: GET /v1/workbench/list
|
||||
@@ -40,6 +40,8 @@ export const routeGroups = {
|
||||
"/workspace/auto-like/:id/edit",
|
||||
"/workspace/auto-group",
|
||||
"/workspace/auto-group/:id",
|
||||
"/workspace/group-create/new",
|
||||
"/workspace/group-create/:id",
|
||||
"/workspace/group-push",
|
||||
"/workspace/group-push/new",
|
||||
"/workspace/group-push/:id",
|
||||
|
||||
@@ -5,6 +5,7 @@ import RecordAutoLike from "@/pages/mobile/workspace/auto-like/record";
|
||||
import AutoGroupList from "@/pages/mobile/workspace/auto-group/list";
|
||||
import AutoGroupDetail from "@/pages/mobile/workspace/auto-group/detail";
|
||||
import AutoGroupForm from "@/pages/mobile/workspace/auto-group/form";
|
||||
import GroupCreateForm from "@/pages/mobile/workspace/group-create/form";
|
||||
import GroupPush from "@/pages/mobile/workspace/group-push/list";
|
||||
import FormGroupPush from "@/pages/mobile/workspace/group-push/form";
|
||||
import DetailGroupPush from "@/pages/mobile/workspace/group-push/detail";
|
||||
@@ -53,7 +54,7 @@ const workspaceRoutes = [
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动建群
|
||||
// 自动建群(旧版)
|
||||
{
|
||||
path: "/workspace/auto-group",
|
||||
element: <AutoGroupList />,
|
||||
@@ -74,6 +75,17 @@ const workspaceRoutes = [
|
||||
element: <AutoGroupForm />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动建群(新版)
|
||||
{
|
||||
path: "/workspace/group-create/new",
|
||||
element: <GroupCreateForm />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-create/:id",
|
||||
element: <GroupCreateForm />,
|
||||
auth: true,
|
||||
},
|
||||
// 群发推送
|
||||
{
|
||||
path: "/workspace/group-push",
|
||||
|
||||
@@ -37,11 +37,12 @@ class DouBaoAI extends Controller
|
||||
|
||||
if (empty($params)){
|
||||
$content = $this->request->param('content', '');
|
||||
$model = $this->request->param('model', 'doubao-seed-1-8-251215');
|
||||
if(empty($content)){
|
||||
return json_encode(['code' => 500, 'msg' => '提示词缺失']);
|
||||
}
|
||||
$params = [
|
||||
'model' => 'doubao-seed-1-8-251215',
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => '你现在是存客宝的AI助理,你精通中国大陆的法律'],
|
||||
['role' => 'user', 'content' => $content],
|
||||
|
||||
@@ -157,6 +157,11 @@ Route::group('v1/', function () {
|
||||
Route::get('userInfoStats', 'app\cunkebao\controller\StatsController@userInfoStats');
|
||||
});
|
||||
|
||||
// 常用功能相关
|
||||
Route::group('common-functions', function () {
|
||||
Route::get('list', 'app\cunkebao\controller\CommonFunctionsController@getList'); // 获取常用功能列表
|
||||
});
|
||||
|
||||
// 算力相关
|
||||
Route::group('tokens', function () {
|
||||
Route::get('list', 'app\cunkebao\controller\TokensController@getList');
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 常用功能控制器
|
||||
*/
|
||||
class CommonFunctionsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取常用功能列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 从数据库查询常用功能列表
|
||||
$functions = Db::name('workbench_function')
|
||||
->where('status', 1)
|
||||
->order('sort ASC, id ASC')
|
||||
->select();
|
||||
|
||||
|
||||
// 处理数据,判断是否显示New标签(创建时间近1个月)
|
||||
$oneMonthAgo = time() - 30 * 24 * 60 * 60; // 30天前的时间戳
|
||||
foreach ($functions as &$function) {
|
||||
// 判断是否显示New标签:创建时间在近1个月内
|
||||
$function['isNew'] = ($function['createTime'] >= $oneMonthAgo) ? true : false;
|
||||
$function['labels'] = json_decode($function['labels'],true);
|
||||
}
|
||||
unset($function);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $functions
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取常用功能列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user