diff --git a/Cunkebao/package.json b/Cunkebao/package.json index 1e1baa4b..82ae71c7 100644 --- a/Cunkebao/package.json +++ b/Cunkebao/package.json @@ -9,6 +9,7 @@ "antd-mobile": "^5.39.1", "antd-mobile-icons": "^0.3.0", "axios": "^1.6.7", + "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "echarts": "^5.6.0", "echarts-for-react": "^3.0.2", @@ -21,6 +22,7 @@ "zustand": "^5.0.6" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/node": "^24.0.14", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", diff --git a/Cunkebao/pnpm-lock.yaml b/Cunkebao/pnpm-lock.yaml index 99f410fa..1ecd31c3 100644 --- a/Cunkebao/pnpm-lock.yaml +++ b/Cunkebao/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: axios: specifier: ^1.6.7 version: 1.11.0 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -54,6 +57,9 @@ importers: specifier: ^5.0.6 version: 5.0.7(@types/react@19.1.10)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) devDependencies: + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 '@types/node': specifier: ^24.0.14 version: 24.2.1 @@ -489,36 +495,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -669,56 +681,67 @@ packages: resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.46.2': resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.46.2': resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.46.2': resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.46.2': resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.46.2': resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.46.2': resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.46.2': resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.46.2': resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.46.2': resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.46.2': resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.46.2': resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} @@ -747,6 +770,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/crypto-js@4.2.2': + resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1016,6 +1042,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -2958,6 +2987,8 @@ snapshots: dependencies: '@babel/types': 7.28.2 + '@types/crypto-js@4.2.2': {} + '@types/estree@1.0.8': {} '@types/node@24.2.1': @@ -3357,6 +3388,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + csstype@3.1.3: {} data-view-buffer@1.0.2: diff --git a/Cunkebao/src/pages/mobile/test/api.ts b/Cunkebao/src/pages/mobile/test/api.ts new file mode 100644 index 00000000..a5ebb039 --- /dev/null +++ b/Cunkebao/src/pages/mobile/test/api.ts @@ -0,0 +1,89 @@ +import axios from "axios"; +import { Toast } from "antd-mobile"; +import { generateSign } from "./utils/sign"; + +// API配置 +const API_BASE_URL = "https://ckbapi.quwanzhi.com/v1/api"; +const API_KEY = "v3pzy-zcfkg-96jio-7xgh6-14kio"; + +export interface SubmitLeadParams { + phone: string; + name: string; + source: string; + remark?: string; + wechatId?: string; + tags?: string; + siteTags?: string; +} + +export interface SubmitLeadResponse { + code: number; + message: string; + data: string | null; +} + +/** + * 提交线索到存客宝 + */ +export async function submitLead( + params: SubmitLeadParams, +): Promise { + try { + // 生成时间戳(秒级) + const timestamp = Math.floor(Date.now() / 1000); + + // 构建请求参数 + const requestParams: Record = { + apiKey: API_KEY, + timestamp, + phone: params.phone, + name: params.name, + source: params.source, + }; + + // 添加可选字段(只添加非空值) + if (params.remark) { + requestParams.remark = params.remark; + } + if (params.wechatId) { + requestParams.wechatId = params.wechatId; + } + if (params.tags) { + requestParams.tags = params.tags; + } + if (params.siteTags) { + requestParams.siteTags = params.siteTags; + } + + // 生成签名 + const sign = generateSign(requestParams, API_KEY); + requestParams.sign = sign; + + // 发送请求 + const response = await axios.post( + `${API_BASE_URL}/scenarios`, + requestParams, + { + headers: { + "Content-Type": "application/json", + }, + timeout: 20000, + }, + ); + + const result = response.data; + + // 处理响应 + if (result.code === 200) { + return result; + } else { + throw new Error(result.message || "提交失败"); + } + } catch (error: any) { + const errorMessage = + error.response?.data?.message || + error.message || + "网络请求失败,请稍后重试"; + throw new Error(errorMessage); + } +} diff --git a/Cunkebao/src/pages/mobile/test/components/TestFormModal.module.scss b/Cunkebao/src/pages/mobile/test/components/TestFormModal.module.scss new file mode 100644 index 00000000..c9108850 --- /dev/null +++ b/Cunkebao/src/pages/mobile/test/components/TestFormModal.module.scss @@ -0,0 +1,47 @@ +.modalContainer { + background: #fff; + min-height: 400px; +} + +.modalHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid #f0f0f0; + position: sticky; + top: 0; + background: #fff; + z-index: 1; +} + +.modalTitle { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #333; +} + +.closeIcon { + font-size: 20px; + color: #666; + cursor: pointer; + padding: 4px; + transition: color 0.2s; + + &:hover { + color: #333; + } +} + +.modalContent { + padding: 20px; +} + +.formFooter { + display: flex; + gap: 12px; + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; +} diff --git a/Cunkebao/src/pages/mobile/test/components/TestFormModal.tsx b/Cunkebao/src/pages/mobile/test/components/TestFormModal.tsx new file mode 100644 index 00000000..95599eef --- /dev/null +++ b/Cunkebao/src/pages/mobile/test/components/TestFormModal.tsx @@ -0,0 +1,173 @@ +import React, { useState } from "react"; +import { Popup, Form, Input, TextArea, Button, Toast } from "antd-mobile"; +import { CloseOutlined } from "@ant-design/icons"; +import styles from "./TestFormModal.module.scss"; +import { submitLead } from "../api"; + +interface TestFormModalProps { + visible: boolean; + onClose: () => void; + onSubmit?: (values: TestFormValues) => void; +} + +export interface TestFormValues { + phone: string; + name: string; + source: string; + remark: string; +} + +const TestFormModal: React.FC = ({ + visible, + onClose, + onSubmit, +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + setLoading(true); + + // 调用API提交数据 + const result = await submitLead({ + phone: values.phone, + name: values.name, + source: values.source, + remark: values.remark || undefined, + }); + + // 调用提交回调(如果提供) + if (onSubmit) { + onSubmit(values as TestFormValues); + } + + Toast.show({ + content: result.message || "提交成功", + icon: "success", + }); + + // 重置表单并关闭弹框 + form.resetFields(); + onClose(); + } catch (error: any) { + console.error("提交失败:", error); + Toast.show({ + content: error.message || "提交失败,请稍后重试", + icon: "fail", + }); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + form.resetFields(); + onClose(); + }; + + return ( + +
+ {/* 头部 */} +
+

测试表单

+ +
+ + {/* 表单内容 */} +
+
+ + +
+ } + > + + + + + + + + + + + + + +