Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop
This commit is contained in:
40
Cunkebao/.eslintrc.js
Normal file
40
Cunkebao/.eslintrc.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint', 'react'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'react-app',
|
||||||
|
'react-app/jest',
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||||
|
],
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'no-console': 'warn',
|
||||||
|
'no-debugger': 'warn',
|
||||||
|
},
|
||||||
|
ignorePatterns: ['node_modules/', 'build/', 'dist/'],
|
||||||
|
};
|
||||||
@@ -309,3 +309,19 @@ export async function createScenarioPlan(data: any) {
|
|||||||
export async function updateScenarioPlan(planId: number | string, data: any) {
|
export async function updateScenarioPlan(planId: number | string, data: any) {
|
||||||
return await put(`/v1/plan/update?planId=${planId}`, data);
|
return await put(`/v1/plan/update?planId=${planId}`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取计划小程序二维码
|
||||||
|
* @param taskid 任务ID
|
||||||
|
* @returns base64二维码
|
||||||
|
*/
|
||||||
|
export const getWxMinAppCode = async (taskId: string): Promise<{ code: number; data?: string; msg?: string }> => {
|
||||||
|
try {
|
||||||
|
return await get<{ code: number; data?: string; msg?: string }>(
|
||||||
|
`/v1/plan/getWxMinAppCode?taskId=${ taskId }`,
|
||||||
|
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return { code: 500, msg: '获取小程序二维码失败' };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Code,
|
Code,
|
||||||
Search,
|
Search,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
QrCode,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
fetchPlanList,
|
fetchPlanList,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
copyPlan,
|
copyPlan,
|
||||||
deletePlan,
|
deletePlan,
|
||||||
type Task,
|
type Task,
|
||||||
|
getWxMinAppCode,
|
||||||
} from "@/api/scenarios";
|
} from "@/api/scenarios";
|
||||||
import { useToast } from "@/components/ui/toast";
|
import { useToast } from "@/components/ui/toast";
|
||||||
import "@/components/Layout.css";
|
import "@/components/Layout.css";
|
||||||
@@ -65,6 +67,10 @@ export default function ScenarioDetail() {
|
|||||||
});
|
});
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [loadingTasks, setLoadingTasks] = useState(false);
|
const [loadingTasks, setLoadingTasks] = useState(false);
|
||||||
|
// 二维码弹窗相关
|
||||||
|
const [showQrDialog, setShowQrDialog] = useState(false);
|
||||||
|
const [qrLoading, setQrLoading] = useState(false);
|
||||||
|
const [qrImg, setQrImg] = useState("");
|
||||||
|
|
||||||
// 获取渠道中文名称
|
// 获取渠道中文名称
|
||||||
const getChannelName = (channel: string) => {
|
const getChannelName = (channel: string) => {
|
||||||
@@ -250,29 +256,6 @@ export default function ScenarioDetail() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStatusChange = async (taskId: string, newStatus: 1 | 0) => {
|
|
||||||
try {
|
|
||||||
// 这里应该调用状态切换API,暂时模拟
|
|
||||||
setTasks((prev) =>
|
|
||||||
prev.map((task) =>
|
|
||||||
task.id === taskId ? { ...task, status: newStatus } : task
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "状态已更新",
|
|
||||||
description: `计划已${newStatus === 1 ? "启动" : "暂停"}`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("状态切换失败:", error);
|
|
||||||
toast({
|
|
||||||
title: "状态切换失败",
|
|
||||||
description: "请稍后重试",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenApiSettings = async (taskId: string) => {
|
const handleOpenApiSettings = async (taskId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetchPlanDetail(taskId);
|
const response = await fetchPlanDetail(taskId);
|
||||||
@@ -310,6 +293,32 @@ export default function ScenarioDetail() {
|
|||||||
navigate(`/scenarios/new/${scenarioId}`);
|
navigate(`/scenarios/new/${scenarioId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShowQrCode = async (taskId: string) => {
|
||||||
|
setShowQrDialog(true);
|
||||||
|
setQrLoading(true);
|
||||||
|
setQrImg("");
|
||||||
|
try {
|
||||||
|
const res = await getWxMinAppCode(taskId);
|
||||||
|
if (res.data) {
|
||||||
|
setQrImg(res.data);
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "获取二维码失败",
|
||||||
|
description: res?.msg || "未知错误",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
title: "获取二维码失败",
|
||||||
|
description: "网络错误",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setQrLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusColor = (status: number) => {
|
const getStatusColor = (status: number) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -463,70 +472,75 @@ export default function ScenarioDetail() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y">
|
<div>
|
||||||
{filteredTasks.map((task) => (
|
{filteredTasks.map((task) => (
|
||||||
<div key={task.id} className="p-4 bg-white">
|
<div
|
||||||
<div className="flex items-center justify-between">
|
key={task.id}
|
||||||
<div className="flex-1">
|
className="p-4 mb-4 bg-white rounded-xl shadow-lg border"
|
||||||
<div className="flex items-center mb-2">
|
>
|
||||||
<h3 className="font-medium text-gray-900">
|
{/* 头部:标题和状态 */}
|
||||||
{task.name}
|
<div className="flex items-center justify-between mb-2">
|
||||||
</h3>
|
<h3 className="font-medium text-gray-900 text-lg">
|
||||||
<span
|
{task.name}
|
||||||
className={`ml-2 px-2 py-1 text-xs rounded-full ${getStatusColor(
|
</h3>
|
||||||
task.status
|
<span
|
||||||
)}`}
|
className={`px-2 py-1 text-xs rounded-full ${getStatusColor(
|
||||||
>
|
task.status
|
||||||
{getStatusText(task.status)}
|
)}`}
|
||||||
</span>
|
>
|
||||||
</div>
|
{getStatusText(task.status)}
|
||||||
<div className="flex items-center text-sm text-gray-500">
|
</span>
|
||||||
<Calendar className="h-4 w-4 mr-1" />
|
</div>
|
||||||
<span>最后更新: {task.lastUpdated}</span>
|
{/* 中部:更新时间和统计 */}
|
||||||
</div>
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-gray-500 mb-2">
|
||||||
<div className="flex items-center mt-2 text-sm text-gray-500">
|
<div className="flex items-center mb-1 md:mb-0">
|
||||||
<span>
|
<Calendar className="h-4 w-4 mr-1" />
|
||||||
设备: {task.stats?.devices || 0} | 获客:{" "}
|
<span>最后更新: {task.lastUpdated}</span>
|
||||||
{task.stats?.acquired || 0} | 添加:{" "}
|
|
||||||
{task.stats?.added || 0}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<div className="flex items-center space-x-2">
|
设备: {task.stats?.devices || 0} | 获客:{" "}
|
||||||
<button
|
{task.stats?.acquired || 0} | 添加:{" "}
|
||||||
onClick={() => navigate(`/scenarios/edit/${task.id}`)}
|
{task.stats?.added || 0}
|
||||||
className={`p-2 rounded-md ${
|
|
||||||
task.status === 1
|
|
||||||
? "text-yellow-600 hover:bg-yellow-50"
|
|
||||||
: "text-green-600 hover:bg-green-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Edit className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => handleOpenApiSettings(task.id)}
|
|
||||||
className="p-2 text-blue-600 hover:bg-blue-50 rounded-md"
|
|
||||||
>
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => handleCopyPlan(task.id)}
|
|
||||||
className="p-2 text-gray-600 hover:bg-gray-50 rounded-md"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => handleDeletePlan(task.id)}
|
|
||||||
className="p-2 text-red-600 hover:bg-red-50 rounded-md"
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* 底部:操作按钮 */}
|
||||||
|
<div className="flex justify-end space-x-2 pt-2 border-t mt-2">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(`/scenarios/edit/${task.id}`)}
|
||||||
|
className={`p-2 rounded-md ${
|
||||||
|
task.status === 1
|
||||||
|
? "text-yellow-600 hover:bg-yellow-50"
|
||||||
|
: "text-green-600 hover:bg-green-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleOpenApiSettings(task.id)}
|
||||||
|
className="p-2 text-blue-600 hover:bg-blue-50 rounded-md"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleCopyPlan(task.id)}
|
||||||
|
className="p-2 text-gray-600 hover:bg-gray-50 rounded-md"
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeletePlan(task.id)}
|
||||||
|
className="p-2 text-red-600 hover:bg-red-50 rounded-md"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleShowQrCode(task.id)}
|
||||||
|
className="p-2 text-gray-600 hover:bg-gray-50 rounded-md"
|
||||||
|
title="小程序二维码"
|
||||||
|
>
|
||||||
|
<QrCode className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -658,6 +672,39 @@ export default function ScenarioDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 二维码弹窗 */}
|
||||||
|
{showQrDialog && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white rounded-xl p-6 max-w-xs w-full flex flex-col items-center relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowQrDialog(false)}
|
||||||
|
className="absolute top-2 right-2 p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-2xl">×</span>
|
||||||
|
</button>
|
||||||
|
<div className="mb-4 flex flex-col items-center">
|
||||||
|
<div className="text-lg font-semibold mb-1">小程序二维码</div>
|
||||||
|
<div className="text-gray-500 text-xs mb-2">
|
||||||
|
可扫码进入对应计划
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{qrLoading ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-40">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-blue-500 mb-2" />
|
||||||
|
<div className="text-gray-400">二维码加载中...</div>
|
||||||
|
</div>
|
||||||
|
) : qrImg ? (
|
||||||
|
<img
|
||||||
|
src={qrImg}
|
||||||
|
alt="二维码"
|
||||||
|
className="w-40 h-40 object-contain border rounded"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="text-red-500">二维码加载失败</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user