This commit is contained in:
ylj20011123 2025-08-22 09:25:44 +08:00
parent 52d3d768e3
commit 56f4ce81f7
10 changed files with 873 additions and 53 deletions

View File

@ -11,6 +11,18 @@ const proxy = {
proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'; proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization';
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true'; proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
} }
},
'/kaipiao': {
target: 'https://cosmic-sandbox.piaozone.com/xhcamzchwbgonerr/',
changeOrigin: true,
secure: false,
pathRewrite: { '^/kaipiao': '' },
onProxyRes: function (proxyRes) {
proxyRes.headers['Access-Control-Allow-Origin'] = '*';
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET,PUT,POST,DELETE,OPTIONS';
proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With';
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
}
} }
} }

View File

@ -17,6 +17,11 @@ export default [
path: '/DigitalElectronics/index', path: '/DigitalElectronics/index',
name: '开票管理', name: '开票管理',
component: "@/pages/DigitalElectronics/index", component: "@/pages/DigitalElectronics/index",
},
{
path: '/InvoiceSearch/index',
name: '开票查询',
component: "@/pages/InvoiceSearch/index",
} }
] ]
} }

View File

@ -227,6 +227,18 @@ const UserModel: UserModelType = {
name: '开票管理', name: '开票管理',
component: "@/pages/Invoicing/index", component: "@/pages/Invoicing/index",
}, },
{
path: '/DigitalElectronics/index',
redirect: '',
name: '开票管理',
component: "@/pages/DigitalElectronics/index",
},
{
path: '/InvoiceSearch/index',
redirect: '',
name: '开票查询',
component: "@/pages/InvoiceSearch/index",
},
] ]
} }
@ -296,7 +308,9 @@ const UserModel: UserModelType = {
authority: [ authority: [
'/test/index', '/test/index',
'/cloudMenu/test/index', '/cloudMenu/test/index',
"/Invoicing/index" "/Invoicing/index",
"/DigitalElectronics/index",
"/InvoiceSearch/index"
// '/examine/index', // '/examine/index',
// '/examine/modal', // '/examine/modal',
// '/examine/question', // '/examine/question',

View File

@ -0,0 +1,483 @@
// 数电的开票
import ProTable, { ActionType } from "@ant-design/pro-table";
import { FormInstance, Button, message, Tag } from "antd";
import { useRef, useState } from "react";
import { connect } from "umi";
import { Datum } from "@/types/invoice";
import { handleGetAppToken, handleGetKaiPiao, handleGetSDToken } from "./service";
interface ConnectState {
user: {
data: any;
};
}
const DigitalElectronics: React.FC<{ currentUser: any }> = (props) => {
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
// 第一个接口的token
const [appToken, setAppToken] = useState<any>()
// 第二个接口的token
const [accessToken, setAccessToken] = useState<any>()
// 开票处理函数
const handleInvoice = async (record: Datum) => {
try {
message.loading({ content: '正在发起开票...', key: 'invoice' });
// 检查是否有access_token
if (!accessToken) {
message.error({ content: '缺少访问令牌,请先刷新页面重新获取', key: 'invoice' });
return;
}
// 构造原始数据
const originalData = [record]; // 将record作为data数组的一项
// 将data字段进行base64加密
const dataString = JSON.stringify(originalData);
const encryptedData = btoa(unescape(encodeURIComponent(dataString))); // base64编码支持中文
// 构造开票请求数据
const invoiceData = {
businessSystemCode: "BUSINESS_YCIC", // 来源系统编码
interfaceCode: "BILL.PUSH", // 接口业务编码
requestId: Date.now().toString(), // 时间戳作为请求ID
data: encryptedData // 加密后的data字符串
};
console.log('原始数据:', originalData);
console.log('JSON字符串:', dataString);
console.log('Base64加密后:', encryptedData);
console.log('最终请求数据:', invoiceData);
console.log('访问令牌:', accessToken);
const response = await handleGetKaiPiao(invoiceData, accessToken);
console.log('开票响应:', response);
if (response && response.code === 200) {
message.success({ content: `单据 ${record.billNo} 开票申请已提交成功`, key: 'invoice' });
} else {
message.error({ content: `开票申请失败: ${response?.message || '未知错误'}`, key: 'invoice' });
}
} catch (error) {
console.error('开票错误:', error);
message.error({ content: '开票申请失败,请重试', key: 'invoice' });
}
};
const columns: any = [
{
title: '单据编号',
dataIndex: 'billNo',
key: 'billNo',
width: 180,
ellipsis: true,
search: true,
hideInSearch: false,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '购方名称',
dataIndex: 'buyerName',
key: 'buyerName',
width: 200,
ellipsis: true,
search: false,
hideInSearch: true,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '购方税号',
dataIndex: 'buyerTaxpayerId',
key: 'buyerTaxpayerId',
width: 180,
ellipsis: true,
search: false,
hideInSearch: true,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '销方名称',
dataIndex: 'sellerName',
key: 'sellerName',
width: 200,
ellipsis: true,
search: false,
hideInSearch: true,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '销方税号',
dataIndex: 'sellerTaxpayerId',
key: 'sellerTaxpayerId',
width: 180,
ellipsis: true,
search: true,
hideInSearch: false,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '单据日期',
dataIndex: 'billDate',
key: 'billDate',
width: 120,
search: false,
hideInSearch: true,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '单据金额',
dataIndex: 'totalAmount',
key: 'totalAmount',
width: 120,
search: false,
hideInSearch: true,
render: (text: number) => text ? `¥${text.toFixed(2)}` : '-',
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '含税标识',
dataIndex: 'includeTaxFlag',
key: 'includeTaxFlag',
width: 100,
search: false,
hideInSearch: true,
render: (text: string) => (
<Tag color={text === '1' ? 'green' : 'blue'}>
{text === '1' ? '含税' : '不含税'}
</Tag>
),
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '发票性质',
dataIndex: 'invoiceProperty',
key: 'invoiceProperty',
width: 100,
search: true,
hideInSearch: false,
valueType: 'select',
valueEnum: {
'0': { text: '蓝票', status: 'Success' },
'1': { text: '红票', status: 'Error' },
},
render: (text: string) => (
<Tag color={text === '1' ? 'red' : 'green'}>
{text === '1' ? '红票' : '蓝票'}
</Tag>
),
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '自动开票',
dataIndex: 'autoInvoice',
key: 'autoInvoice',
width: 100,
search: false,
hideInSearch: true,
render: (text: string) => (
<Tag color={text === '1' ? 'green' : 'default'}>
{text === '1' ? '是' : '否'}
</Tag>
),
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
width: 150,
ellipsis: true,
search: false,
hideInSearch: true,
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'center',
search: false,
hideInSearch: true,
render: (text: any, record: Datum) => (
<Button
type="primary"
size="small"
onClick={() => handleInvoice(record)}
>
</Button>
),
onHeaderCell: () => ({
style: { textAlign: 'center' }
}),
},
]
return (
<div>
<style>
{`
.ant-table-tbody > tr > td {
text-align: left !important;
}
.ant-table-thead > tr > th {
text-align: center !important;
}
`}
</style>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
rowKey={(record) => {
return `${record?.billNo}`
}}
scroll={{ x: "100%", y: 'calc(100vh - 400px)' }}
headerTitle={<span style={{ color: "#1890ff", fontSize: 14, fontWeight: 600 }}></span>}
search={{
span: 8,
labelWidth: 100,
searchText: '查询',
resetText: '重置',
collapseRender: false,
collapsed: false
}}
request={async (params: any) => {
let req: any = {
appId: "FPY001",
appSecret: "FPY001fpy@2023***",
accountId: "2280459335882518528",
tenantid: "xhcamzchwbgonerr",
language: ""
}
const token = await handleGetAppToken(req)
console.log('tokentokentoken', token);
let appToken: any = token.data.app_token
let tokenReq: any = {
user: "18620126214",
apptoken: appToken,
tenantid: "xhcamzchwbgonerr",
accountId: "2280459335882518528",
usertype: ""
}
const accessToken: any = await handleGetSDToken(tokenReq)
console.log('accessTokenaccessTokenaccessToken', accessToken);
setAppToken(appToken)
setAccessToken(accessToken.data.access_token)
// 模拟数据
const mockData: any = [
{
billNo: "SRM_20250820001",
billDate: "2023-05-17",
totalAmount: 11300,
autoInvoice: "0",
invoiceType: "08xdp",
buyerName: "深圳市顺丰快运有限公司test01",
buyerProperty: "0",
sellerTaxpayerId: "91530112MA7MQ2JR9U",
sellerName: "云南交投集团经营开发有限公司彩云驿商业管理分公司",
sellerBankAndAccount: "13710884704",
sellerAddressAndTel: "高新技术产业园南区科技南十二路58996989",
drawer: "开票人",
billDetail: [
{
amount: 11300,
detailId: "1194121661989796666",
goodsName: "蜂胶口腔膜",
lineProperty: 2,
price: 11300,
quantity: 1,
revenueCode: "1070304990000000000",
taxRate: "0.13",
units: "个"
}
]
},
{
billNo: "SRM_20250820002",
billDate: "2025-08-20",
buyerName: "深圳电子有限公司",
buyerTaxpayerId: "91440300123456789C",
sellerName: "云南交投集团经营开发有限公司彩云驿商业管理分公司",
sellerTaxpayerId: "91530112MA7MQ2JR9U",
totalAmount: 1.13,
includeTaxFlag: "1",
buyerProperty: "0",
sellerBankAndAccount: "",
sellerAddressAndTel: "",
pushMatchRules: "3",
drawer: "",
invoiceProperty: "0",
autoInvoice: "0",
invoiceType: "08xdp",
buyerRecipientPhone: "18158132615",
buyerRecipientMail: "",
remark: "设备采购款",
billDetail: [
{
amount: 1,
detailId: "1194121661989796666",
goodsName: "蜂胶口腔膜",
lineProperty: 1,
price: 11300,
quantity: 1,
revenueCode: "1070304990000000000",
taxRate: "0.13",
units: "个"
}
]
},
{
billNo: "SRM_20250820003",
billDate: "2025-08-19",
buyerName: "杭州互联网有限公司",
buyerTaxpayerId: "91330100123456789E",
sellerName: "云南交投集团经营开发有限公司彩云驿商业管理分公司",
sellerTaxpayerId: "91530112MA7MQ2JR9U",
totalAmount: 1.13,
includeTaxFlag: "1",
buyerProperty: "0",
sellerBankAndAccount: "",
pushMatchRules: "3",
sellerAddressAndTel: "",
drawer: "",
invoiceProperty: "0",
autoInvoice: "1",
invoiceType: "08xdp",
buyerRecipientPhone: "18158132615",
buyerRecipientMail: "",
remark: "技术咨询服务",
billDetail: [
{
amount: 1,
detailId: "1194121661989796666",
goodsName: "蜂胶口腔膜",
lineProperty: 2,
price: 1,
quantity: 1,
revenueCode: "1070304990000000000",
taxRate: "0.13",
units: "个"
}
]
},
{
billNo: "SRM_20250820004",
billDate: "2025-08-18",
buyerName: "成都科技有限公司",
buyerTaxpayerId: "91510100123456789G",
sellerName: "云南交投集团经营开发有限公司彩云驿商业管理分公司",
sellerTaxpayerId: "91530112MA7MQ2JR9U",
totalAmount: 1.13,
includeTaxFlag: "1",
buyerProperty: "0",
sellerBankAndAccount: "",
sellerAddressAndTel: "",
pushMatchRules: "3",
drawer: "",
invoiceProperty: "1",
autoInvoice: "0",
invoiceType: "08xdp",
buyerRecipientPhone: "18158132615",
buyerRecipientMail: "",
remark: "物流服务费红冲",
billDetail: [
{
amount: 1,
detailId: "1194121661989796666",
goodsName: "蜂胶口腔膜",
lineProperty: 1,
price: 11300,
quantity: 1,
revenueCode: "1070304990000000000",
taxRate: "0.13",
units: "个"
}
]
},
{
billNo: "SRM_20250820005",
billDate: "2025-08-17",
buyerName: "西安制造有限公司",
buyerTaxpayerId: "91610100123456789I",
sellerName: "云南交投集团经营开发有限公司彩云驿商业管理分公司",
sellerTaxpayerId: "91530112MA7MQ2JR9U",
totalAmount: 1.13,
includeTaxFlag: "1",
buyerProperty: "0",
sellerBankAndAccount: "",
sellerAddressAndTel: "",
pushMatchRules: "3",
drawer: "",
invoiceProperty: "0",
autoInvoice: "1",
invoiceType: "08xdp",
buyerRecipientPhone: "18158132615",
buyerRecipientMail: "",
remark: "原材料采购",
billDetail: [
{
amount: 1,
detailId: "1194121661989796666",
goodsName: "蜂胶口腔膜",
lineProperty: 2,
price: 1,
quantity: 1,
revenueCode: "1070304990000000000",
taxRate: "0.13",
units: "个"
}
]
}
];
return {
data: mockData,
success: true,
total: mockData.length
}
}}
/>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.data
}))(DigitalElectronics);

View File

@ -0,0 +1,44 @@
import requestKaiPiao from "@/utils/requestKaiPiao"
// 获取app_token
export async function handleGetAppToken(params?: any) {
const data = await requestKaiPiao.post('/api/getAppToken.do', params)
return data
}
// 获取 token
export async function handleGetSDToken(params?: any) {
const data = await requestKaiPiao.post('/api/login.do', params)
return data
}
// 开票申请
export async function handleGetKaiPiao(bodyData: any, accessToken: string) {
const data = await requestKaiPiao.post('/kapi/app/sim/openApi', bodyData, {
params: {
access_token: accessToken
},
headers: {
'access_token': accessToken
}
})
return data
}
// 开票申请单发票查询
export async function handleGetInvoiceSearch(bodyData: any, accessToken: string) {
const data = await requestKaiPiao.post('/kapi/app/sim/openApi', bodyData, {
params: {
access_token: accessToken
},
headers: {
'access_token': accessToken
}
})
return data
}

View File

@ -0,0 +1,2 @@
// 开票查询

View File

@ -1,52 +0,0 @@
// 数电的开票
import ProTable, { ActionType } from "@ant-design/pro-table";
import { FormInstance } from "antd";
import { useRef } from "react";
import { connect } from "umi";
const DigitalElectronics: React.FC<{ currentUser: any }> = (props) => {
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const columns: any = []
return (
<div>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
expandable={{
expandRowByClick: true
}}
rowKey={(record) => {
return `${record?.id}`
}}
scroll={{ x: "100%", y: 'calc(100vh - 400px)' }}
headerTitle={<span style={{ color: "#1890ff", fontSize: 14, fontWeight: 600 }}></span>}
search={{ span: 6 }}
request={async (params: any) => {
if (tableData.list && tableData.list.length > 0) {
return {
data: tableData.list,
success: true,
total: tableData.total, // 总数据条数
current: tableData.pageNo, // 当前页码
pageSize: tableData.pageSize, // 每页条数
}
}
return { data: [], success: true }
}}
/>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.data
}))(DigitalElectronics);

View File

@ -1,6 +1,8 @@
const authority: PageAuthority = { const authority: PageAuthority = {
'/test/index': ['/test/index'], '/test/index': ['/test/index'],
'/Invoicing/index': ['/Invoicing/index'], '/Invoicing/index': ['/Invoicing/index'],
'/DigitalElectronics/index': ['/DigitalElectronics/index'],
'/InvoiceSearch/index': ['/InvoiceSearch/index'],
}; };

148
src/types/invoice.ts Normal file
View File

@ -0,0 +1,148 @@
// 开票相关接口定义
export interface ApifoxModel {
/**
* SRM系统SRM
*/
businessSystemCode: string;
/**
* data的值加密成字符串base64
*/
data: Datum[];
/**
* 使BILL.PUSH
*/
interfaceCode: InterfaceCode;
/**
* ID使UUID如1624601109096
*/
requestId: string;
[property: string]: any;
}
export interface Datum {
/**
* []6.0.2
*/
account?: string;
/**
* []0- 1-2 10
*/
applicant?: string;
/**
* []0-1-0
*/
autoInvoice?: string;
/**
* [] 0-1-1
*/
autoMerge?: string;
/**
* yyyy-MM-dd)
*/
billDate?: string;
/**
*
*/
billDetail?: BillDetail[];
/**
*
*/
billDiscountAmount?: number;
/**
* 线-:Srm_20250518001
*/
billNo: string;
/**
* []
*/
buyerName?: string;
/**
* []
*/
buyerTaxpayerId?: string;
/**
* 0-1-10
*/
includeTaxFlag?: string;
/**
* 01
*/
invoiceProperty?: string;
/**
*
*/
invoiceType?: string;
/**
* []
*/
sellerName?: string;
/**
* []/ 1 2/
*/
sellerTaxpayerId?: string;
/**
* GBK编码230字节230
*/
remark?: string;
/**
* 使
*/
totalAmount?: number;
[property: string]: any;
}
export interface BillDetail {
/**
* includeTaxFlag=1
*/
amount: number;
/**
* idid 50
*/
detailId: string;
/**
* 21
*/
goodsCode?: string;
/**
* 21GBK92字节
*/
goodsName?: string;
/**
* 1()2
*/
lineProperty: string;
/**
* 16131price有值则quantity必填includeTaxFlag=1
*/
price?: string;
/**
* 16131quantity有值则price必填
*/
quantity?: string;
/**
*
*/
revenueCode?: string;
/**
* GBK40字节
*/
specification?: string;
/**
* (14,2)
*/
taxAmount?: string;
/**
* 0.130.090.06
*/
taxRate?: string;
/**
* GBK22字节
*/
units?: string;
[property: string]: any;
}
export enum InterfaceCode {
BillPush = "BILL.PUSH",
}

162
src/utils/requestKaiPiao.ts Normal file
View File

@ -0,0 +1,162 @@
import axios from 'axios';
import { getDvaApp } from 'umi';
import { notification } from 'antd';
import type { AxiosRequestHeaders } from 'axios/index';
import CryptoJS from "crypto-js";
const { UMI_APP_BASEURL } = process.env;
// const instance = axios.create({ baseURL: UMI_APP_BASEURL });
// const instance = axios.create({ baseURL: 'https://api.eshangtech.com/EShangApiMain' });
// const instance = axios.create({ baseURL: 'http://home.robot-z.cn:7001/' });
// 修改baseURL为完整的API地址确保在生产环境中正确访问
// 从直接访问外部URL
// const instance = axios.create({ baseURL: 'https://cosmic-sandbox.piaozone.com/jdpjykjyxgs' });
// 改为使用代理路径
const instance = axios.create({ baseURL: '/kaipiao' });
instance.interceptors.request.use(
(config) => {
// 对data数据进行加密
// if (config.data) {
// config.data = preprocessData(JSON.stringify(config.data)); // 调用预处理函数
// }
console.log('config', config);
const isUpload = config.url?.includes("/oss/upload");
config.headers = {
...config.headers,
Authorization: `Bearer ${localStorage.getItem('Authorization') || ''}`,
"Content-Type": isUpload ? "multipart/form-data" : "application/json;charset=utf-8",
} as AxiosRequestHeaders;
return config;
},
(error) => Promise.reject(error),
);
instance.interceptors.response.use(
//状态码为2xx的时候执行
(response) => {
const { data } = response;
// if (data.code !== 200) {
// notification.error({
// message: data.message,
// });
// }
const timestamp = getFormattedDate()
return data
},
//状态码不是2xx的时候执行
(error) => {
const { response } = error;
if (response && response.status === 401) {
// // 清除本地存储的token
// localStorage.removeItem('Authorization');
// // 重定向到登录页
// window.location.href = '/user/login';
// notification.error({
// message: response?.data?.message || '请求失败',
// description: error.message
// });
} else {
notification.error({
message: response?.data?.message || '请求失败',
description: error.message
});
}
return Promise.reject({
code: response?.status || 500,
message: response?.data?.message || '请求失败'
});
},
);
// 加密
const encryptAESECB = (data: string, key: string) => {
// const cipher = CryptoJS.createCipheriv('aes-128-ecb', key, null); // ECB 模式不需要 IV
const newKey = CryptoJS.enc.Utf8.parse(key); // 密钥必须是 16 字节
const cipher = CryptoJS.AES.encrypt(data, newKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
let encrypted = cipher.ciphertext.toString(CryptoJS.enc.Hex);
// let encrypted = cipher.update(data, 'utf8', 'hex');
// encrypted += cipher.final('hex');
return encrypted;
}
// 解密
const decryptAESECB = (data: string, key: string) => {
// const decipher = CryptoJS.createDecipheriv('aes-128-ecb', key, null);
// let decrypted = decipher.update(data, 'hex', 'utf8');
// decrypted += decipher.final('utf8');
const newKey = CryptoJS.enc.Utf8.parse(key);
const encryptedData = CryptoJS.enc.Hex.parse(data);
// 解密操作
const decrypted = CryptoJS.AES.decrypt({ ciphertext: encryptedData }, newKey, {
mode: CryptoJS.mode.ECB, // ECB 模式
padding: CryptoJS.pad.Pkcs7 // PKCS7 填充方式
});
// 将解密后的结果转为 UTF-8 字符串
const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedText;
}
// md5 签名
const md5 = (key: string, data: string, timestamp: string) => {
const text = "s" + key + data + timestamp;
return CryptoJS.MD5(text).toString(CryptoJS.enc.Hex);
}
// 生成签名戳
const getFormattedDate = () => {
const date = new Date();
const year = date.getFullYear(); // 获取年份 (yyyy)
const month = String(date.getMonth() + 1).padStart(2, '0'); // 获取月份 (MM)
const day = String(date.getDate()).padStart(2, '0'); // 获取日期 (dd)
const hours = String(date.getHours()).padStart(2, '0'); // 获取小时 (HH)
return `es0${year}${month}${day}${hours}0es`; // 拼接成 yyyyMMddHH 格式
}
// 加密方法
const preprocessData = (data: string) => {
console.log('data', data);
// YYYYMMDD
let timestamp = getFormattedDate()
console.log('timestamp', timestamp);
// 秒为单位的时间戳
let timeSecond = parseInt((new Date().getTime() / 1000).toString())
console.log('timeSecond', timeSecond);
// 数据的加密
let encryptionData = encryptAESECB(data, timestamp)
console.log('encryptionData', encryptionData);
// md5签名方法
let md5Data = md5(timestamp, encryptionData, timestamp)
console.log('md5Data', md5Data);
let res = {
data: encryptionData,
timestamp: timeSecond,
sign: md5Data
}
console.log('res', res);
return res
}
export default instance;