设备基本信息设置
This commit is contained in:
@@ -33,6 +33,22 @@ export const fetchDeviceDetail = async (id: string | number): Promise<ApiRespons
|
||||
return api.get<ApiResponse<any>>(`/v1/devices/${id}`);
|
||||
};
|
||||
|
||||
// 更新设备任务配置
|
||||
export const updateDeviceTaskConfig = async (
|
||||
id: string | number,
|
||||
config: {
|
||||
autoAddFriend?: boolean;
|
||||
autoReply?: boolean;
|
||||
momentsSync?: boolean;
|
||||
aiChat?: boolean;
|
||||
}
|
||||
): Promise<ApiResponse<any>> => {
|
||||
return api.post<ApiResponse<any>>(`/v1/devices/task-config`, {
|
||||
id,
|
||||
...config
|
||||
});
|
||||
};
|
||||
|
||||
// 删除设备
|
||||
export const deleteDevice = async (id: number): Promise<ApiResponse<any>> => {
|
||||
return api.delete<ApiResponse<any>>(`/v1/devices/${id}`);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { fetchDeviceDetail } from "@/api/devices"
|
||||
import { fetchDeviceDetail, updateDeviceTaskConfig } from "@/api/devices"
|
||||
import { toast } from "sonner"
|
||||
|
||||
interface WechatAccount {
|
||||
@@ -37,7 +37,7 @@ interface Device {
|
||||
features: {
|
||||
autoAddFriend: boolean
|
||||
autoReply: boolean
|
||||
contentSync: boolean
|
||||
momentsSync: boolean
|
||||
aiChat: boolean
|
||||
}
|
||||
history: {
|
||||
@@ -68,6 +68,12 @@ export default function DeviceDetailPage() {
|
||||
const [device, setDevice] = useState<Device | null>(null)
|
||||
const [activeTab, setActiveTab] = useState("info")
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [savingFeatures, setSavingFeatures] = useState({
|
||||
autoAddFriend: false,
|
||||
autoReply: false,
|
||||
momentsSync: false,
|
||||
aiChat: false
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!params.id) return
|
||||
@@ -91,16 +97,47 @@ export default function DeviceDetailPage() {
|
||||
historicalIds: [], // 服务端暂无此数据
|
||||
wechatAccounts: [], // 默认空数组
|
||||
history: [], // 服务端暂无此数据
|
||||
features: serverData.features || {
|
||||
features: {
|
||||
autoAddFriend: false,
|
||||
autoReply: false,
|
||||
contentSync: false,
|
||||
momentsSync: false,
|
||||
aiChat: false
|
||||
},
|
||||
totalFriend: serverData.totalFriend || 0,
|
||||
thirtyDayMsgCount: serverData.thirtyDayMsgCount || 0
|
||||
}
|
||||
|
||||
// 解析features
|
||||
if (serverData.features) {
|
||||
// 如果后端直接返回了features对象,使用它
|
||||
formattedDevice.features = {
|
||||
autoAddFriend: Boolean(serverData.features.autoAddFriend),
|
||||
autoReply: Boolean(serverData.features.autoReply),
|
||||
momentsSync: Boolean(serverData.features.momentsSync || serverData.features.contentSync),
|
||||
aiChat: Boolean(serverData.features.aiChat)
|
||||
}
|
||||
} else if (serverData.taskConfig) {
|
||||
try {
|
||||
// 解析taskConfig字段
|
||||
let taskConfig = serverData.taskConfig
|
||||
if (typeof taskConfig === 'string') {
|
||||
taskConfig = JSON.parse(taskConfig)
|
||||
}
|
||||
|
||||
if (taskConfig) {
|
||||
console.log('解析的taskConfig:', taskConfig);
|
||||
formattedDevice.features = {
|
||||
autoAddFriend: Boolean(taskConfig.autoAddFriend),
|
||||
autoReply: Boolean(taskConfig.autoReply),
|
||||
momentsSync: Boolean(taskConfig.momentsSync),
|
||||
aiChat: Boolean(taskConfig.aiChat)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('解析taskConfig失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有微信账号信息,构建微信账号对象
|
||||
if (serverData.wechatId) {
|
||||
formattedDevice.wechatAccounts = [
|
||||
@@ -169,7 +206,7 @@ export default function DeviceDetailPage() {
|
||||
features: {
|
||||
autoAddFriend: true,
|
||||
autoReply: true,
|
||||
contentSync: false,
|
||||
momentsSync: false,
|
||||
aiChat: true,
|
||||
},
|
||||
history: [
|
||||
@@ -193,6 +230,74 @@ export default function DeviceDetailPage() {
|
||||
fetchDevice()
|
||||
}, [params.id])
|
||||
|
||||
// 处理功能开关状态变化
|
||||
const handleFeatureChange = async (feature: keyof Device['features'], checked: boolean) => {
|
||||
if (!device) return
|
||||
|
||||
// 避免已经在处理中的功能被重复触发
|
||||
if (savingFeatures[feature]) {
|
||||
return
|
||||
}
|
||||
|
||||
setSavingFeatures(prev => ({ ...prev, [feature]: true }))
|
||||
|
||||
try {
|
||||
// 准备更新后的功能状态
|
||||
const updatedFeatures = { ...device.features, [feature]: checked }
|
||||
|
||||
// 创建API请求参数
|
||||
const configUpdate = { [feature]: checked }
|
||||
|
||||
// 立即更新UI状态,提供即时反馈
|
||||
setDevice(prev => prev ? {
|
||||
...prev,
|
||||
features: updatedFeatures
|
||||
} : null)
|
||||
|
||||
// 调用API更新服务器配置
|
||||
const response = await updateDeviceTaskConfig(device.id, configUpdate)
|
||||
|
||||
if (response && response.code === 200) {
|
||||
toast.success(`${getFeatureName(feature)}${checked ? '已启用' : '已禁用'}`)
|
||||
} else {
|
||||
// 如果请求失败,回滚UI变更
|
||||
setDevice(prev => prev ? {
|
||||
...prev,
|
||||
features: { ...prev.features, [feature]: !checked }
|
||||
} : null)
|
||||
|
||||
// 处理错误信息,使用类型断言解决字段不一致问题
|
||||
const anyResponse = response as any;
|
||||
const errorMsg = anyResponse ? (anyResponse.message || anyResponse.msg || '未知错误') : '未知错误';
|
||||
toast.error(`更新失败: ${errorMsg}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`更新${getFeatureName(feature)}失败:`, error)
|
||||
|
||||
// 异常情况下也回滚UI变更
|
||||
setDevice(prev => prev ? {
|
||||
...prev,
|
||||
features: { ...prev.features, [feature]: !checked }
|
||||
} : null)
|
||||
|
||||
toast.error('更新失败,请稍后重试')
|
||||
} finally {
|
||||
setSavingFeatures(prev => ({ ...prev, [feature]: false }))
|
||||
}
|
||||
}
|
||||
|
||||
// 获取功能中文名称
|
||||
const getFeatureName = (feature: string): string => {
|
||||
const nameMap: Record<string, string> = {
|
||||
autoAddFriend: '自动加好友',
|
||||
autoReply: '自动回复',
|
||||
momentsSync: '朋友圈同步',
|
||||
aiChat: 'AI会话'
|
||||
}
|
||||
|
||||
return nameMap[feature] || feature
|
||||
}
|
||||
|
||||
if (loading || !device) {
|
||||
return <div>加载中...</div>
|
||||
}
|
||||
@@ -261,28 +366,68 @@ export default function DeviceDetailPage() {
|
||||
<Label>自动加好友</Label>
|
||||
<div className="text-sm text-gray-500">自动通过好友验证</div>
|
||||
</div>
|
||||
<Switch checked={device.features.autoAddFriend} />
|
||||
<div className="flex items-center">
|
||||
{savingFeatures.autoAddFriend && (
|
||||
<div className="w-4 h-4 mr-2 rounded-full border-2 border-blue-500 border-t-transparent animate-spin"></div>
|
||||
)}
|
||||
<Switch
|
||||
checked={Boolean(device.features.autoAddFriend)}
|
||||
onCheckedChange={(checked) => handleFeatureChange('autoAddFriend', checked)}
|
||||
disabled={savingFeatures.autoAddFriend}
|
||||
className="data-[state=checked]:bg-blue-500 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>自动回复</Label>
|
||||
<div className="text-sm text-gray-500">自动回复好友消息</div>
|
||||
</div>
|
||||
<Switch checked={device.features.autoReply} />
|
||||
<div className="flex items-center">
|
||||
{savingFeatures.autoReply && (
|
||||
<div className="w-4 h-4 mr-2 rounded-full border-2 border-blue-500 border-t-transparent animate-spin"></div>
|
||||
)}
|
||||
<Switch
|
||||
checked={Boolean(device.features.autoReply)}
|
||||
onCheckedChange={(checked) => handleFeatureChange('autoReply', checked)}
|
||||
disabled={savingFeatures.autoReply}
|
||||
className="data-[state=checked]:bg-blue-500 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>朋友圈同步</Label>
|
||||
<div className="text-sm text-gray-500">自动同步朋友圈内容</div>
|
||||
</div>
|
||||
<Switch checked={device.features.contentSync} />
|
||||
<div className="flex items-center">
|
||||
{savingFeatures.momentsSync && (
|
||||
<div className="w-4 h-4 mr-2 rounded-full border-2 border-blue-500 border-t-transparent animate-spin"></div>
|
||||
)}
|
||||
<Switch
|
||||
checked={Boolean(device.features.momentsSync)}
|
||||
onCheckedChange={(checked) => handleFeatureChange('momentsSync', checked)}
|
||||
disabled={savingFeatures.momentsSync}
|
||||
className="data-[state=checked]:bg-blue-500 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label>AI会话</Label>
|
||||
<div className="text-sm text-gray-500">启用AI智能对话</div>
|
||||
</div>
|
||||
<Switch checked={device.features.aiChat} />
|
||||
<div className="flex items-center">
|
||||
{savingFeatures.aiChat && (
|
||||
<div className="w-4 h-4 mr-2 rounded-full border-2 border-blue-500 border-t-transparent animate-spin"></div>
|
||||
)}
|
||||
<Switch
|
||||
checked={Boolean(device.features.aiChat)}
|
||||
onCheckedChange={(checked) => handleFeatureChange('aiChat', checked)}
|
||||
disabled={savingFeatures.aiChat}
|
||||
className="data-[state=checked]:bg-blue-500 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -16,6 +16,7 @@ Route::group('v1/', function () {
|
||||
Route::post('', 'app\\devices\\controller\\Device@save'); // 添加设备
|
||||
Route::put('refresh', 'app\\devices\\controller\\Device@refresh'); // 刷新设备状态
|
||||
Route::delete(':id', 'app\\devices\\controller\\Device@delete'); // 删除设备
|
||||
Route::post('task-config', 'app\\devices\\controller\\Device@updateTaskConfig'); // 更新设备任务配置
|
||||
});
|
||||
|
||||
// 设备微信相关
|
||||
|
||||
@@ -396,4 +396,73 @@ class Device extends Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备任务配置
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateTaskConfig()
|
||||
{
|
||||
// 获取请求参数
|
||||
$data = $this->request->post();
|
||||
|
||||
// 验证参数
|
||||
if (empty($data['id'])) {
|
||||
return json(['code' => 400, 'msg' => '设备ID不能为空']);
|
||||
}
|
||||
|
||||
// 转换为整型,确保ID格式正确
|
||||
$deviceId = intval($data['id']);
|
||||
|
||||
// 先获取设备信息,确认设备存在且未删除
|
||||
$device = \app\devices\model\Device::where('id', $deviceId)
|
||||
->where('isDeleted', 0)
|
||||
->find();
|
||||
|
||||
if (!$device) {
|
||||
return json(['code' => 404, 'msg' => '设备不存在或已删除']);
|
||||
}
|
||||
|
||||
// 读取原taskConfig,如果存在则解析
|
||||
$taskConfig = [];
|
||||
if (!empty($device['taskConfig'])) {
|
||||
$taskConfig = json_decode($device['taskConfig'], true) ?: [];
|
||||
}
|
||||
|
||||
// 更新需要修改的配置项
|
||||
$updateFields = ['autoAddFriend', 'autoReply', 'momentsSync', 'aiChat'];
|
||||
$hasUpdate = false;
|
||||
|
||||
foreach ($updateFields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
// 将值转换为布尔类型存储
|
||||
$taskConfig[$field] = (bool)$data[$field];
|
||||
$hasUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有需要更新的字段,直接返回成功
|
||||
if (!$hasUpdate) {
|
||||
return json(['code' => 200, 'msg' => '更新成功', 'data' => ['taskConfig' => $taskConfig]]);
|
||||
}
|
||||
|
||||
// 更新设备taskConfig字段
|
||||
$result = \app\devices\model\Device::where('id', $deviceId)
|
||||
->update([
|
||||
'taskConfig' => json_encode($taskConfig),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新任务配置成功',
|
||||
'data' => [
|
||||
'taskConfig' => $taskConfig
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
return json(['code' => 500, 'msg' => '更新任务配置失败']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@ class WechatAccount extends Model
|
||||
'gender' => 'integer',
|
||||
'currentDeviceId' => 'integer',
|
||||
'isDeleted' => 'integer',
|
||||
'groupId' => 'integer'
|
||||
'groupId' => 'integer',
|
||||
'status' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user