This commit is contained in:
ylj20011123 2025-06-17 20:07:10 +08:00
parent f873afdf74
commit b72cf7e03d
14 changed files with 1187 additions and 0 deletions

View File

@ -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', path: '/Invoicing',
name: 'Invoicing', name: 'Invoicing',

View File

@ -204,6 +204,62 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
// 拿到所有枚举的方法 // 拿到所有枚举的方法
const handleGetAllFieldEnum = async () => { 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' }) const SETTLEMENT_MODESList = await getFieldEnum({ FieldExplainField: 'SETTLEMENT_MODES' })
session.set('SETTLEMENT_MODESList', SETTLEMENT_MODESList); session.set('SETTLEMENT_MODESList', SETTLEMENT_MODESList);
@ -426,6 +482,10 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
}) })
session.set('serverpartObj', obj); session.set('serverpartObj', obj);
} }
} }
// 显示就调用 // 显示就调用

View File

@ -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<any>()
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const ModalFormRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>()
const [printIndex, setPrintIndex] = useState<number>(new Date().getTime())
// 树相关的属性和方法
const [selectedId, setSelectedId] = useState<string>()
// 导出的加载效果
const [showLoading, setShowLoading] = useState<boolean>(false)
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 查询的条件
const [searchParams, setSearchParams] = useState<any>()
// 显示详情的抽屉
const [showDetail, setShowDetail] = useState<boolean>(false)
// 当前行数据
const [currentRow, setCurrentRow] = useState<any>()
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 <Space>
<a onClick={() => {
setShowDetail(true)
setCurrentRow(record)
}}></a>
<Popconfirm title={"确认删除该规则?"} onConfirm={async () => {
let req: any = {
}
const data = await handleDeleteCOOPSHOP_RULE(req)
}}>
<a></a>
</Popconfirm>
</Space>
}
}
]
return (
<div ref={(el) => {
// 打印报表
if (!reqDetailList || reqDetailList.length === 0) return;
setPrintOut(el);
}} >
<div style={{ backgroundColor: '#fff', display: 'flex' }}>
{/* <LeftSelectTree setSelectedId={setSelectedId} setCollapsible={setCollapsible} collapsible={collapsible} /> */}
<div style={{
width: '100%',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
// headerTitle={<span style={{ color: "#1890ff", fontSize: 14, fontWeight: 600 }}>单品销售排行统计</span>}
headerTitle={<PageTitleBox props={props} />}
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: [
<Button type={"primary"} onClick={() => {
setShowDetail(true)
}}></Button>
]
}}
/>
</div>
</div>
<Modal
open={showDetail}
onCancel={() => {
setCurrentRow(undefined)
setShowDetail(false)
}}
width={'70%'}
title={currentRow?.COOPSHOP_RULE_ID ? currentRow?.COOPSHOP_RULE_NAME : "新增规则"}
>
<ProForm formRef={ModalFormRef} layout={'horizontal'} submitter={false} initialValues={{
UPDATE_STAFF_NAME: currentUser?.Name,
UPDATE_DATE: moment().format('YYYY-MM-DD HH:mm:ss'),
}}>
<Divider orientation="left"></Divider>
<Row gutter={8}>
<Col span={6}>
<ProFormSelect
label={"发券业主"}
name={"OPERATINGUNIT_NAME"}
/>
</Col>
<Col span={6}>
<ProFormSelect
label={"运营单位"}
name={"OPERATINGUNIT_NAME"}
/>
</Col>
<Col span={6}>
<ProFormText
label={"规则名称"}
name={"OPERATINGUNIT_NAME"}
/>
</Col>
<Col span={6}>
<ProFormSelect
label={"是否有效"}
name={"OPERATINGUNIT_NAME"}
/>
</Col>
<Col span={6}>
<ProFormSelect
label={"线下领用"}
name={"OPERATINGUNIT_NAME"}
/>
</Col>
<Col span={6}>
<ProFormText
label={"创建人员"}
name={"CREATE_STAFF_NAME"}
disabled
/>
</Col>
<Col span={6}>
<ProFormText
label={"更新人员"}
name={"UPDATE_STAFF_NAME"}
disabled
/>
</Col>
<Col span={6}>
<ProFormText
label={"更新时间"}
name={"UPDATE_DATE"}
disabled
/>
</Col>
</Row>
</ProForm>
</Modal>
</div >
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(CardCouponApplicRules);

View File

@ -0,0 +1 @@
// 卡券兑换统计

View File

@ -0,0 +1 @@
// 卡券信息管理

View File

@ -0,0 +1 @@
// 卡券库存管理

View File

@ -0,0 +1 @@
// 卡券领取统计

View File

@ -0,0 +1 @@
// 卡券领取查询

View File

@ -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
}

View File

@ -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<any>()
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>()
const [printIndex, setPrintIndex] = useState<number>(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<string>()
// 导出的加载效果
const [showLoading, setShowLoading] = useState<boolean>(false)
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 查询的条件
const [searchParams, setSearchParams] = useState<any>()
// 表格数据
const [tableData, setTableData] = useState<any>()
// 表格的加载效果
const [tableLoading, setTableLoading] = useState<boolean>(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 (
<div ref={(el) => {
// 打印报表
if (!reqDetailList || reqDetailList.length === 0) return;
setPrintOut(el);
}} >
{
showLoading ?
<div
style={{
width: '100%',
height: '100%',
background: 'rgba(0,0,0,0.1)',
position: 'fixed',
zIndex: 5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '15px 20px 10px',
background: '#fff',
borderRadius: '8px',
width: '200px'
}}>
<Spin />
<span style={{ marginLeft: '5px' }}>...</span>
</div>
</div> : ''
}
<div className={`saleReportHideBox${printIndex}`} style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }}>
{
showExportTable && reqDetailList && reqDetailList.length > 0 ?
<ProTable
columns={columns}
dataSource={reqDetailList}
pagination={false}
expandable={{
defaultExpandAllRows: true
}}
/> : ''
}
</div>
<div id='hiddenBox' style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }} />
<div style={{ backgroundColor: '#fff', display: 'flex' }}>
<div style={{
width: '100%',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
scroll={{ x: '100%', y: 'calc(100vh - 450px)' }}
headerTitle={<PageTitleBox props={props} />}
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} 条记录`
}}
/>
</div>
</div>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(memberInfor);

View File

@ -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
}

158
src/utils/crypto-js.js Normal file
View File

@ -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;

48
src/utils/handleAes.js Normal file
View File

@ -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 '';
}
}

View File

@ -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<number, string> = {
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;