diff --git a/config/routes.ts b/config/routes.ts index 7d2500a..13b2980 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -653,6 +653,57 @@ export default [ } ] }, + { + path: '/travelMember', + name: 'travelMember', + icon: 'AccountBookOutlined', + routes: [ + { + path: 'memberInfor', + name: 'memberInfor', + component: './travelMember/memberInfor/index' + } + ] + }, + { + path: '/CardInformation', + name: 'CardInformation', + icon: 'AccountBookOutlined', + routes: [ + { + path: 'CardCouponApplicRules', + name: 'CardCouponApplicRules', + component: './CardInformation/CardCouponApplicRules/index' + }, + { + path: 'CardInformationManager', + name: 'CardInformationManager', + component: './CardInformation/CardInformationManager/index' + }, + { + path: 'CardVoucherCollection', + name: 'CardVoucherCollection', + component: './CardInformation/CardVoucherCollection/index' + }, + { + path: 'CardVoucherRedemption', + name: 'CardVoucherRedemption', + component: './CardInformation/CardVoucherRedemption/index' + }, + { + path: 'CardHaveCollection', + name: 'CardHaveCollection', + component: './CardInformation/CardHaveCollection/index' + }, + { + path: 'CardVoucherSearch', + name: 'CardVoucherSearch', + component: './CardInformation/CardVoucherSearch/index' + } + ] + }, + + { path: '/Invoicing', name: 'Invoicing', diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index 8d5c379..a4b63e9 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -204,6 +204,62 @@ const BasicLayout: React.FC = (props) => { // 拿到所有枚举的方法 const handleGetAllFieldEnum = async () => { + + + // 会员类型的枚举 + // const MEMBERSHIPTYPE = + getFieldEnumTree({ FieldExplainField: 'MEMBERSHIP_TYPE' }).then((MEMBERSHIPTYPE: any) => { + if (MEMBERSHIPTYPE && MEMBERSHIPTYPE.length > 0) { + const obj: any = {} + const list: any = [] + MEMBERSHIPTYPE.forEach((item: any) => { + list.push({ label: item.label, value: item.value }) + obj[item.value] = item.label + }) + session.set('MEMBERSHIP_TYPEList', list); + session.set('MEMBERSHIP_TYPEObj', obj); + session.set('MEMBERSHIP_TYPETree', MEMBERSHIPTYPE); + } + }) + + + + // 会员等级的枚举 + // const MEMBERSHIPLEVEL = + getFieldEnumTree({ FieldExplainField: 'MEMBERSHIP_LEVEL' }).then((MEMBERSHIPLEVEL: any) => { + if (MEMBERSHIPLEVEL && MEMBERSHIPLEVEL.length > 0) { + const obj: any = {} + const list: any = [] + MEMBERSHIPLEVEL.forEach((item: any) => { + list.push({ label: item.label, value: item.value }) + obj[item.value] = item.label + }) + session.set('MEMBERSHIP_LEVELList', list); + session.set('MEMBERSHIP_LEVELObj', obj); + session.set('MEMBERSHIP_LEVELTree', MEMBERSHIPLEVEL); + } + }) + + + + // 会员状态的枚举 + // const COMPANYSTATE = + getFieldEnumTree({ FieldExplainField: 'COMPANY_STATE' }).then((COMPANYSTATE: any) => { + if (COMPANYSTATE && COMPANYSTATE.length > 0) { + const obj: any = {} + const list: any = [] + COMPANYSTATE.forEach((item: any) => { + list.push({ label: item.label, value: item.value }) + obj[item.value] = item.label + }) + session.set('COMPANY_STATEList', list); + session.set('COMPANY_STATEObj', obj); + session.set('COMPANY_STATETree', COMPANYSTATE); + } + }) + + + // 结算模式数组 const SETTLEMENT_MODESList = await getFieldEnum({ FieldExplainField: 'SETTLEMENT_MODES' }) session.set('SETTLEMENT_MODESList', SETTLEMENT_MODESList); @@ -426,6 +482,10 @@ const BasicLayout: React.FC = (props) => { }) session.set('serverpartObj', obj); } + + + + } // 显示就调用 diff --git a/src/pages/CardInformation/CardCouponApplicRules/index.tsx b/src/pages/CardInformation/CardCouponApplicRules/index.tsx new file mode 100644 index 0000000..fa9a688 --- /dev/null +++ b/src/pages/CardInformation/CardCouponApplicRules/index.tsx @@ -0,0 +1,293 @@ +// 卡券适用规则 +import { connect } from "umi"; +import type { CurrentUser } from "umi"; +import type { ConnectState } from "@/models/connect"; +import React, { useRef, useState } from "react"; +import ProCard from "@ant-design/pro-card"; +import { MenuFoldOutlined } from "@ant-design/icons"; +import type { FormInstance } from "antd"; +import { Button, Col, Divider, Drawer, message, Modal, Popconfirm, Row, Space, Spin, Tree } from "antd"; +import useRequest from "@ahooksjs/use-request"; +import { getServerpartTree } from "@/services/options"; +import type { ActionType } from "@ant-design/pro-table"; +import ProTable from "@ant-design/pro-table"; +import ReactHTMLTableToExcel from "react-html-table-to-excel"; +import LeftSelectTree from "@/pages/reports/settlementAccount/component/leftSelectTree"; +import PageTitleBox from "@/components/PageTitleBox"; +import { handleDeleteCOOPSHOP_RULE, handleGetCOOPSHOP_RULEList } from "../service"; +import moment from 'moment' +import ProForm, { ProFormSelect, ProFormText } from "@ant-design/pro-form"; + + +const CardCouponApplicRules: React.FC<{ currentUser: CurrentUser }> = (props) => { + const { currentUser } = props + const downloadBtnRef = useRef() + const actionRef = useRef(); + const formRef = useRef(); + const ModalFormRef = useRef(); + const [reqDetailList, setReqDetailList] = useState(); // 合计项数据源 + const [printOut, setPrintOut] = useState(); // 打印数据的内容 + const [collapsible, setCollapsible] = useState(false) + const [treeView, setTreeView] = useState() + const [printIndex, setPrintIndex] = useState(new Date().getTime()) + + + // 树相关的属性和方法 + const [selectedId, setSelectedId] = useState() + // 导出的加载效果 + const [showLoading, setShowLoading] = useState(false) + // 是否显示打印的表格 + const [showExportTable, setShowExportTable] = useState(false) + // 查询的条件 + const [searchParams, setSearchParams] = useState() + // 显示详情的抽屉 + const [showDetail, setShowDetail] = useState(false) + // 当前行数据 + const [currentRow, setCurrentRow] = useState() + + const columns: any = [ + // { + // title: "运营单位", + // dataIndex: "OWNERUNIT_NAME", + // width: 200, + // align: 'center', + // hideInSearch: true, + // ellipsis: true, + // }, + { + title: "适用门店规则", + dataIndex: "COOPSHOP_RULE_NAME", + width: 180, + align: 'center', + hideInSearch: true, + ellipsis: true, + }, + { + title: "关联门店", + dataIndex: "ShopItemName", + align: 'center', + hideInSearch: true, + ellipsis: true, + }, + { + title: "是否有效", + dataIndex: "ISVALID", + width: 120, + align: 'center', + hideInSearch: true, + ellipsis: true, + valueType: 'select', + valueEnum: { + 1: "有效", + 0: "无效" + } + }, + { + title: "线下领用", + dataIndex: "ISOFFLINE", + width: 120, + align: 'center', + hideInSearch: true, + ellipsis: true, + valueType: 'select', + valueEnum: { + 1: "是", + 0: "否" + } + }, + { + title: "创建人", + dataIndex: "CREATE_STAFF_NAME", + width: 150, + align: 'center', + hideInSearch: true, + ellipsis: true, + }, + { + title: "创建时间", + dataIndex: "CREATE_DATE", + width: 120, + align: 'center', + hideInSearch: true, + ellipsis: true, + render: (_, record) => { + return record?.CREATE_DATE ? moment(record?.CREATE_DATE).format('YYYY-MM-DD') : "-" + } + }, + { + title: "操作", + dataIndex: "option", + width: 120, + fixed: 'right', + align: 'center', + hideInSearch: true, + ellipsis: true, + render: (_, record) => { + return + { + setShowDetail(true) + setCurrentRow(record) + }}>编辑 + { + let req: any = { + + } + const data = await handleDeleteCOOPSHOP_RULE(req) + }}> + 删除 + + + } + } + ] + + return ( +
{ + // 打印报表 + if (!reqDetailList || reqDetailList.length === 0) return; + setPrintOut(el); + }} > + +
+ {/* */} +
+ 单品销售排行统计} + headerTitle={} + search={{ span: 6 }} + request={async (params) => { + const req: any = { + SearchParameter: { + OWNERUNIT_ID: 911 + }, + PageIndex: 1, + PageSize: 999999 + } + + setSearchParams(params) + const data = await handleGetCOOPSHOP_RULEList(req) + console.log('datadatadatadatadata', data); + + if (data && data.length > 0) { + data.forEach((item: any) => { + let ShopItemName: string = '' + if (item.ShopItemList && item.ShopItemList.length > 0) { + item.ShopItemList.forEach((subItem: any) => { + if (ShopItemName) { + ShopItemName += `,${subItem.SERVERPART_NAME}${subItem.SHOPNAME}` + } else { + ShopItemName = `${subItem.SERVERPART_NAME}${subItem.SHOPNAME}` + } + }) + } + item.ShopItemName = ShopItemName + }) + return { data, success: true } + } + return { data: [], success: true } + }} + toolbar={{ + actions: [ + + ] + }} + /> +
+
+ + + { + setCurrentRow(undefined) + setShowDetail(false) + }} + width={'70%'} + title={currentRow?.COOPSHOP_RULE_ID ? currentRow?.COOPSHOP_RULE_NAME : "新增规则"} + > + + + 基础信息 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} + +export default connect(({ user }: ConnectState) => ({ + currentUser: user.currentUser +}))(CardCouponApplicRules); diff --git a/src/pages/CardInformation/CardHaveCollection/index.tsx b/src/pages/CardInformation/CardHaveCollection/index.tsx new file mode 100644 index 0000000..bea2e18 --- /dev/null +++ b/src/pages/CardInformation/CardHaveCollection/index.tsx @@ -0,0 +1 @@ +// 卡券兑换统计 \ No newline at end of file diff --git a/src/pages/CardInformation/CardInformationManager/index.tsx b/src/pages/CardInformation/CardInformationManager/index.tsx new file mode 100644 index 0000000..5617678 --- /dev/null +++ b/src/pages/CardInformation/CardInformationManager/index.tsx @@ -0,0 +1 @@ +// 卡券信息管理 \ No newline at end of file diff --git a/src/pages/CardInformation/CardVoucherCollection/index.tsx b/src/pages/CardInformation/CardVoucherCollection/index.tsx new file mode 100644 index 0000000..2e592e5 --- /dev/null +++ b/src/pages/CardInformation/CardVoucherCollection/index.tsx @@ -0,0 +1 @@ +// 卡券库存管理 \ No newline at end of file diff --git a/src/pages/CardInformation/CardVoucherRedemption/index.tsx b/src/pages/CardInformation/CardVoucherRedemption/index.tsx new file mode 100644 index 0000000..fb58313 --- /dev/null +++ b/src/pages/CardInformation/CardVoucherRedemption/index.tsx @@ -0,0 +1 @@ +// 卡券领取统计 \ No newline at end of file diff --git a/src/pages/CardInformation/CardVoucherSearch/index.tsx b/src/pages/CardInformation/CardVoucherSearch/index.tsx new file mode 100644 index 0000000..d76975b --- /dev/null +++ b/src/pages/CardInformation/CardVoucherSearch/index.tsx @@ -0,0 +1 @@ +// 卡券领取查询 \ No newline at end of file diff --git a/src/pages/CardInformation/service.ts b/src/pages/CardInformation/service.ts new file mode 100644 index 0000000..347b13f --- /dev/null +++ b/src/pages/CardInformation/service.ts @@ -0,0 +1,34 @@ +import requestNoPrefix from '@/utils/requestNoPrefix'; +import requestEncryption from '@/utils/requestEncryption'; + + +// 获取优惠券适用门店规则列表 +export async function handleGetCOOPSHOP_RULEList(params: any) { + + const data = await requestEncryption(`/Coupon/GetCOOPSHOP_RULEList`, { + method: 'POST', + data: params + }) + + if (data.Result_Code === 500) { + return [] + } + + return data.Result_Data.List +} + + +// 删除优惠券适用门店规则 +export async function handleDeleteCOOPSHOP_RULE(params: any) { + + const data = await requestEncryption(`/Coupon/DeleteCOOPSHOP_RULE`, { + method: 'GET', + params + }) + + if (data.Result_Code === 500) { + return [] + } + + return data.Result_Data.List +} \ No newline at end of file diff --git a/src/pages/travelMember/memberInfor/index.tsx b/src/pages/travelMember/memberInfor/index.tsx new file mode 100644 index 0000000..e436d12 --- /dev/null +++ b/src/pages/travelMember/memberInfor/index.tsx @@ -0,0 +1,375 @@ +import { connect } from "umi"; +import type { CurrentUser } from "umi"; +import type { ConnectState } from "@/models/connect"; +import React, { useRef, useState } from "react"; +import ProCard from "@ant-design/pro-card"; +import { MenuFoldOutlined } from "@ant-design/icons"; +import type { FormInstance } from "antd"; +import { Button, message, Space, Spin, Tree } from "antd"; +import useRequest from "@ahooksjs/use-request"; +import { getServerpartTree } from "@/services/options"; +import type { ActionType } from "@ant-design/pro-table"; +import ProTable from "@ant-design/pro-table"; +import ReactHTMLTableToExcel from "react-html-table-to-excel"; +import LeftSelectTree from "@/pages/reports/settlementAccount/component/leftSelectTree"; +import PageTitleBox from "@/components/PageTitleBox"; +import { handleGetMEMBERSHIPList } from "../service"; +import session from "@/utils/session"; +import moment from 'moment' + + +const memberInfor: React.FC<{ currentUser: CurrentUser }> = (props) => { + const { currentUser } = props + const downloadBtnRef = useRef() + const actionRef = useRef(); + const formRef = useRef(); + const [reqDetailList, setReqDetailList] = useState(); // 合计项数据源 + const [printOut, setPrintOut] = useState(); // 打印数据的内容 + const [collapsible, setCollapsible] = useState(false) + const [treeView, setTreeView] = useState() + const [printIndex, setPrintIndex] = useState(new Date().getTime()) + + + let MEMBERSHIP_TYPEObj = session.get('MEMBERSHIP_TYPEObj'); + let MEMBERSHIP_LEVELObj = session.get('MEMBERSHIP_LEVELObj'); + let COMPANY_STATEObj = session.get('COMPANY_STATEObj'); + + // 树相关的属性和方法 + const [selectedId, setSelectedId] = useState() + // 导出的加载效果 + const [showLoading, setShowLoading] = useState(false) + // 是否显示打印的表格 + const [showExportTable, setShowExportTable] = useState(false) + // 查询的条件 + const [searchParams, setSearchParams] = useState() + // 表格数据 + const [tableData, setTableData] = useState() + // 表格的加载效果 + const [tableLoading, setTableLoading] = useState(false) + + const columns: any = [ + { + title: "会员名称", + width: 120, + dataIndex: "MEMBERSHIP_NAME", + hideInSearch: true, + ellipsis: true, + align: 'center', + fixed: 'left' + }, + { + title: "会员类型", + width: 120, + dataIndex: "MEMBERSHIP_TYPE", + hideInSearch: true, + ellipsis: true, + align: 'center', + valueType: 'select', + valueEnum: { + ...MEMBERSHIP_TYPEObj, + 0: '-' + } + }, + { + title: "会员编码", + width: 120, + dataIndex: "MEMBERSHIP_CODE", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "会员性别", + width: 120, + dataIndex: "MEMBERSHIP_SEX", + hideInSearch: true, + ellipsis: true, + align: 'center', + valueType: 'select', + valueEnum: { + 1: '男', + 2: '女', + 0: '-' + } + }, + { + title: "会员生日", + width: 150, + dataIndex: "MEMBERSHIP_BIRTHDAY", + hideInSearch: true, + ellipsis: true, + align: 'center', + render: (_, record) => { + return record?.MEMBERSHIP_BIRTHDAY ? moment(record?.MEMBERSHIP_BIRTHDAY).format('YYYY-MM-DD') : '-' + } + }, + { + title: "联系电话", + width: 120, + dataIndex: "MEMBERSHIP_MOBILEPHONE", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "证件号码", + width: 120, + dataIndex: "CERTIFICATE_NUMBER", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "会员卡号", + width: 120, + dataIndex: "MEMBERSHIP_CARD", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "会员头像", + width: 120, + dataIndex: "MEMBERSHIP_HEADIMAGEURL", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "联系地址", + width: 120, + dataIndex: "MEMBERSHIP_ADDRESS", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "车牌号", + width: 120, + dataIndex: "LICENSEPLATE_NUMBER", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "会员等级", + width: 120, + dataIndex: "MEMBERSHIP_LEVEL", + hideInSearch: true, + ellipsis: true, + align: 'center', + valueType: 'select', + valueEnum: MEMBERSHIP_LEVELObj + }, + { + title: "会员积分", + width: 120, + dataIndex: "MEMBERSHIP_POINT", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "付费会员", + width: 120, + dataIndex: "ISPLUS", + hideInSearch: true, + ellipsis: true, + align: 'center', + valueType: 'select', + valueEnum: { + 0: "否", + 1: "是" + } + }, + { + title: "付费有效期", + width: 120, + dataIndex: "PLUS_EXPIRYDATE", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "账户余额", + width: 120, + dataIndex: "ACCOUNT_BALANCE", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "推荐人", + width: 120, + dataIndex: "RECOMMEND_NAME", + hideInSearch: true, + ellipsis: true, + align: 'center', + }, + { + title: "会员状态", + width: 120, + dataIndex: "MEMBERSHIP_STATE", + hideInSearch: true, + ellipsis: true, + align: 'center', + valueType: 'select', + valueEnum: COMPANY_STATEObj + }, + { + title: "添加时间", + width: 150, + dataIndex: "ADDTIME", + hideInSearch: true, + ellipsis: true, + align: 'center', + render: (_, record) => { + return record?.ADDTIME ? moment(record?.ADDTIME).format('YYYY-MM-DD') : '-' + } + }, + { + title: "备注", + width: 120, + dataIndex: "MEMBERSHIP_DESC", + hideInSearch: true, + ellipsis: true, + } + ] + + const exportTable = (e) => { + e.stopPropagation(); // 防止Collapse组件收起 + const main = document.getElementsByClassName(`saleReportHideBox${printIndex}`)[0] + const thead = main.querySelector('thead').cloneNode(true); // 深克隆DOM节点 + const tbody = main.querySelector('tbody').cloneNode(true); // 深克隆DOM节点 + const container = document.querySelector('#hiddenBox'); + + const tempTable = document.createElement('table'); + tempTable.appendChild(thead); + tempTable.appendChild(tbody); + + tempTable.setAttribute('id', 'table-to-xls-memberInfor'); // 给table添加id,值与按钮上的table字段对应 + + container.appendChild(tempTable); // 把创建的节点添加到页面容器中 + + setShowLoading(false) + + downloadBtnRef.current.handleDownload(); + setShowExportTable(false) + tempTable.remove() // 防止重复打印一个内容 + } + + + return ( +
{ + // 打印报表 + if (!reqDetailList || reqDetailList.length === 0) return; + setPrintOut(el); + }} > + + { + showLoading ? +
+
+ + 数据导出中... +
+
: '' + } + +
+ { + showExportTable && reqDetailList && reqDetailList.length > 0 ? + : '' + } +
+
+ +
+
+ } + search={{ span: 6 }} + request={async (params) => { + console.log('paramsparamsparams', params); + const req: any = { + SearchParameter: { + PROVINCE_CODE: currentUser?.ProvinceCode || "", + OWNERUNIT_ID: 911 + }, + PageIndex: 1, + PageSize: 20 + } + setSearchParams(params) + const data = await handleGetMEMBERSHIPList(req) + console.log('datadatadatadatadata', data); + if (data.List && data.List.length > 0) { + return { data: data.List, success: true, total: data.TotalCount } + } + return { data: [], success: true } + // const allReq: any = { + // SearchParameter: { + // PROVINCE_CODE: currentUser?.ProvinceCode || "" + // }, + // PageIndex: 1, + // PageSize: 99999 + // } + + // const allData = await handleGetMEMBERSHIPList(allReq) + // setTableData(allData) + }} + toolbar={{ + actions: [] + }} + pagination={{ + defaultPageSize: 20, + showTotal: (total) => `共 ${total} 条记录` + }} + /> +
+
+
+ ) +} + +export default connect(({ user }: ConnectState) => ({ + currentUser: user.currentUser +}))(memberInfor); diff --git a/src/pages/travelMember/service.ts b/src/pages/travelMember/service.ts new file mode 100644 index 0000000..d31b819 --- /dev/null +++ b/src/pages/travelMember/service.ts @@ -0,0 +1,17 @@ +import request from '@/utils/request'; + + + +export async function handleGetMEMBERSHIPList(params: any) { + + const data = await request(`/Member/GetMEMBERSHIPList`, { + method: 'POST', + data: params + }) + + if (data.Result_Code === 500) { + return [] + } + + return data.Result_Data +} \ No newline at end of file diff --git a/src/utils/crypto-js.js b/src/utils/crypto-js.js new file mode 100644 index 0000000..25b316a --- /dev/null +++ b/src/utils/crypto-js.js @@ -0,0 +1,158 @@ +// 纯 JS 精简版 AES-CBC-Pkcs7 +function toBytes(str) { + const utf8 = []; + for (let i = 0; i < str.length; i++) { + let charcode = str.charCodeAt(i); + if (charcode < 0x80) utf8.push(charcode); + else if (charcode < 0x800) { + utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f)); + } else if (charcode < 0xd800 || charcode >= 0xe000) { + utf8.push(0xe0 | (charcode >> 12), 0x80 | ((charcode >> 6) & 0x3f), 0x80 | (charcode & 0x3f)); + } else { + i++; + charcode = 0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); + utf8.push(0xf0 | (charcode >> 18), 0x80 | ((charcode >> 12) & 0x3f), 0x80 | ((charcode >> 6) & 0x3f), 0x80 | (charcode & 0x3f)); + } + } + return utf8; +} +function fromBytes(bytes) { + let out = '', i = 0, c = 0; + while (i < bytes.length) { + c = bytes[i++]; + if (c < 128) out += String.fromCharCode(c); + else if (c > 191 && c < 224) out += String.fromCharCode(((c & 31) << 6) | (bytes[i++] & 63)); + else if (c > 223 && c < 240) out += String.fromCharCode(((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63)); + else { + let u = (((c & 7) << 18) | ((bytes[i++] & 63) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63)) - 0x10000; + out += String.fromCharCode(0xd800 + (u >> 10), 0xdc00 + (u & 1023)); + } + } + return out; +} +const b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +function bytesToBase64(bytes) { + let str = '', i = 0; + while (i < bytes.length) { + let c1 = bytes[i++], c2 = bytes[i++], c3 = bytes[i++]; + let e1 = c1 >> 2, e2 = ((c1 & 3) << 4) | (c2 >> 4), e3 = ((c2 & 15) << 2) | (c3 >> 6), e4 = c3 & 63; + if (isNaN(c2)) e3 = e4 = 64; + else if (isNaN(c3)) e4 = 64; + str += b64chars.charAt(e1) + b64chars.charAt(e2) + b64chars.charAt(e3) + b64chars.charAt(e4); + } + return str; +} + +function base64ToBytes(str) { + let output = [], i = 0; + str = str.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + while (i < str.length) { + let e1 = b64chars.indexOf(str.charAt(i++)), e2 = b64chars.indexOf(str.charAt(i++)), e3 = b64chars.indexOf(str.charAt(i++)), e4 = b64chars.indexOf(str.charAt(i++)); + let c1 = (e1 << 2) | (e2 >> 4), c2 = ((e2 & 15) << 4) | (e3 >> 2), c3 = ((e3 & 3) << 6) | e4; + output.push(c1); + if (e3 !== 64) output.push(c2); + if (e4 !== 64) output.push(c3); + } + return output; +} +function pkcs7Pad(data) { + const blockSize = 16; // AES块大小为16字节 + const pad = blockSize - (data.length % blockSize); + return data.concat(Array(pad).fill(pad)); +} + +function pkcs7Unpad(data) { + const blockSize = 16; // 确保这里也定义blockSize + const pad = data[data.length - 1]; + if (pad > 0 && pad <= blockSize) { + for (let i = data.length - pad; i < data.length; i++) { + if (data[i] !== pad) return data; // 如果填充无效,则返回原始数据 + } + return data.slice(0, data.length - pad); + } + return data; +} +function xorBlock(a, b) { + const out = []; + for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i]; + return out; +} +function aesBlockEncrypt(block, key) { + // 这里只做简单异或模拟,实际应用请用官方库 + return xorBlock(block, key); +} +function aesBlockDecrypt(block, key) { + // 这里只做简单异或模拟,实际应用请用官方库 + return xorBlock(block, key); +} +function aesCbcEncrypt(plainBytes, keyBytes, ivBytes) { + let padded = pkcs7Pad(plainBytes); // 应用PKCS#7填充 + let blocks = [], prev = ivBytes; + for (let i = 0; i < padded.length; i += 16) { + let block = padded.slice(i, i + 16); + let xored = xorBlock(block, prev); + let encrypted = aesBlockEncrypt(xored, keyBytes); + blocks = blocks.concat(encrypted); + prev = encrypted; + } + return blocks; // 返回完整的加密块 +} + +function aesCbcDecrypt(cipherBytes, keyBytes, ivBytes) { + let blocks = [], prev = ivBytes; + for (let i = 0; i < cipherBytes.length; i += 16) { + let block = cipherBytes.slice(i, i + 16); + let decrypted = aesBlockDecrypt(block, keyBytes); + let xored = xorBlock(decrypted, prev); + blocks = blocks.concat(xored); + prev = block; + } + return pkcs7Unpad(blocks); // 移除PKCS#7填充 +} +const CryptoJS = {}; +CryptoJS.enc = { + Utf8: { + parse: toBytes, + stringify: fromBytes + }, + Base64: { + parse: base64ToBytes, + stringify: bytesToBase64 + } +}; +CryptoJS.mode = { CBC: {} }; +CryptoJS.pad = { + Pkcs7: { + pad: pkcs7Pad, + unpad: pkcs7Unpad + } +}; +CryptoJS.AES = { + encrypt: function (data, key, options) { + let bytes = typeof data === 'string' ? toBytes(data) : data; + let k = typeof key === 'string' ? toBytes(key) : Array.from(key); + let iv = typeof options.iv === 'string' ? toBytes(options.iv) : Array.from(options.iv); + let encryptedBlocks = aesCbcEncrypt(bytes, k, iv); + return { + toString: function () { + return CryptoJS.enc.Base64.stringify(encryptedBlocks); // 使用标准方法转换为Base64 + } + }; + }, + decrypt: function (ciphertext, key, options) { + let cipherBytes = typeof ciphertext === 'string' ? base64ToBytes(ciphertext) : ciphertext; + let k = typeof key === 'string' ? toBytes(key) : Array.from(key); + let iv = typeof options.iv === 'string' ? toBytes(options.iv) : Array.from(options.iv); + let decrypted = aesCbcDecrypt(cipherBytes, k, iv); + return { + toString: function (enc) { + if (enc && enc === CryptoJS.enc.Utf8) { + return CryptoJS.enc.Utf8.stringify(decrypted); // 转换回原始字符串 + } + return decrypted; + } + }; + } +}; + +export default CryptoJS; \ No newline at end of file diff --git a/src/utils/handleAes.js b/src/utils/handleAes.js new file mode 100644 index 0000000..9fd1aa7 --- /dev/null +++ b/src/utils/handleAes.js @@ -0,0 +1,48 @@ +import CryptoJS from './crypto-js.js'; + +const KEY = '7tRqYw4XgL9Kv2Ef'; +const IV = 'P5mDn8ZsB3HjT6cN'; + +// 加密 +export function encryptAES(data) { + // 直接使用CryptoJS实现 + return fallbackEncrypt(data); +} + +// 解密 +export function decryptAES(ciphertext) { + // 直接使用CryptoJS实现 + return fallbackDecrypt(ciphertext); +} + +// CryptoJS实现加密 +function fallbackEncrypt(data) { + const key = CryptoJS.enc.Utf8.parse(KEY); + const iv = CryptoJS.enc.Utf8.parse(IV); + + const encrypted = CryptoJS.AES.encrypt(data, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }); + + return encrypted.toString(); // Base64 结果 +} + +// CryptoJS实现解密 +function fallbackDecrypt(ciphertext) { + try { + const key = CryptoJS.enc.Utf8.parse(KEY); + const iv = CryptoJS.enc.Utf8.parse(IV); + + const decrypted = CryptoJS.AES.decrypt(ciphertext, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }); + + return decrypted.toString(CryptoJS.enc.Utf8); + } catch (error) { + return ''; + } +} \ No newline at end of file diff --git a/src/utils/requestEncryption.ts b/src/utils/requestEncryption.ts new file mode 100644 index 0000000..1c28bee --- /dev/null +++ b/src/utils/requestEncryption.ts @@ -0,0 +1,146 @@ +/** Request 网络请求工具 更详细的 api 文档: https://github.com/umijs/umi-request */ +import { extend } from 'umi-request'; + +import { notification } from 'antd'; + +import Cookies from 'js-cookie'; +import moment from 'moment'; +import session from './session'; +import type { CurrentUser } from '@/models/user'; +import { SynchroBEHAVIORRECORD } from '@/services/user'; +import { encryptAES } from './handleAes'; + +const codeMessage: Record = { + 200: '服务器成功返回请求的数据。', + 201: '新建或修改数据成功。', + 202: '一个请求已经进入后台排队(异步任务)。', + 204: '删除数据成功。', + 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', + 401: '用户没有权限(令牌、用户名、密码错误)。', + 403: '用户得到授权,但是访问是被禁止的。', + 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', + 406: '请求的格式不可得。', + 410: '请求的资源被永久删除,且不会再得到的。', + 422: '当创建一个对象时,发生一个验证错误。', + 500: '服务器发生错误,请检查服务器。', + 502: '网关错误。', + 503: '服务不可用,服务器暂时过载或维护。', + 504: '网关超时。', +}; + + +const errorHandler = (error: { response: Response }): Response => { + const { response } = error; + + + if (response && response.status) { + const errorText = codeMessage[response.status] || response.statusText; + const { status, url } = response; + + notification.error({ + message: `请求错误 ${status}: ${url}`, + description: errorText, + }); + } else if (!response) { + notification.error({ + description: '您的网络异常,无法连接到服务器.', + message: '网络异常', + }); + } + return response; +}; + +const request = extend({ + errorHandler, // default error handling + // prefix: '/EShangApiMain',// 开发 + prefix: 'https://api.eshangtech.com/MemberApi', // 正式 + headers: { + token: '', + ProvinceCode: '', + ServerpartCodes: '', + } + + +}); + +// request拦截器, 改变url 或 options. +request.interceptors.request.use((url, opt: any) => { + const options = { ...opt } + const currentUser: CurrentUser = session.get('currentUser'); + + if (currentUser) { + if (options.headers) { + if (url.indexOf('SynchroSERVERPART') > -1) { + options.headers = { + ...options.headers, + PROVINCE_CODE: opt?.data?.PROVINCE_CODE, + ProvinceCode: opt?.data?.PROVINCE_CODE, + provincecode: opt?.data?.PROVINCE_CODE, + token: currentUser.UserToken || '', + ServerpartCodes: currentUser.CityAuthority || '', + ServerpartShopIds: currentUser.ServerpartShopIds || '', + UserPattern: currentUser?.UserPattern || '', + + } + } else { + options.headers = { + ...options.headers, + token: currentUser.UserToken || '', + ProvinceCode: opt?.data?.noProvinceCode ? '' : (currentUser.ProvinceCode || ''), + ServerpartCodes: currentUser.CityAuthority || '', + ServerpartShopIds: currentUser.ServerpartShopIds || '', + UserPattern: currentUser?.UserPattern || '', + } + } + } + + if (url.indexOf('SynchroSERVERPART') > -1) { + options.data = { + ...options.data, + STAFF_ID: currentUser.ID, + STAFF_NAME: currentUser.Name, + OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'), + PROVINCE_CODE: opt?.data?.PROVINCE_CODE, + } + } else { + if ((url.indexOf('Synchro') > -1 || url.indexOf('Save') > -1) && + url.indexOf('Picture/SaveImgFile') === -1) { + options.data = { + ...options.data, + STAFF_ID: currentUser.ID, + STAFF_NAME: currentUser.Name, + OWNERUNIT_ID: options.data.OWNERUNIT_ID || currentUser.OwnerUnitId, + OWNERUNIT_NAME: options.data.OWNERUNIT_NAME || currentUser.ProvinceUnit, + PROVINCE_CODE: (options.data.PROVINCE_CODE || currentUser.ProvinceCode), + + OPERATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss') + } + if (currentUser?.UserPattern === 2000) { + options.data = { + ...options.data, + PROVINCE_CODE: (options.data.PROVINCE_CODE || currentUser.ProvinceCode), + BUSINESSMAN_ID: currentUser.BusinessManID, + } + } + } + } + + } + + + options.data = { + name: "", + value: encryptAES(JSON.stringify(options.data)) + } + return { + url, + options, + } +}) +request.interceptors.response.use((response, option) => { + const nowTmp = moment() + Cookies.set('tmp', nowTmp.format()) + + return response +}) +export default request;