InvoicingDev 是一個(gè)基于 React 的發(fā)票管理系統,主要用于出口退稅申報、報關(guān)單管理、發(fā)票開(kāi)具等外貿相關(guān)業(yè)務(wù)。項目采用現代化的前端技術(shù)棧,具備完整的業(yè)務(wù)功能模塊。
src/
├── components/ # 組件目錄
│ ├── ckfp/ # 出口發(fā)票模塊
│ ├── txrp/ # 退稅申報模塊
│ ├── txhx/ # 核銷(xiāo)模塊
│ ├── wmqtsb/ # 外貿其他申報
│ └── layout/ # 布局組件
├── reducers/ # Redux狀態(tài)管理
├── servers/ # 服務(wù)層
├── utils/ # 工具函數
└── public/ # 靜態(tài)資源
項目采用組件化開(kāi)發(fā)模式,每個(gè)業(yè)務(wù)模塊都有獨立的組件目錄,實(shí)現高內聚低耦合。
使用 Redux 進(jìn)行全局狀態(tài)管理,主要包含:
userReducer
- 用戶(hù)信息管理commonReducer
- 通用狀態(tài)管理采用 React Router 實(shí)現SPA路由,支持嵌套路由和權限控制。
# 安裝依賴(lài)
npm install
# 開(kāi)發(fā)模式啟動(dòng)
npm start
# 構建生產(chǎn)版本
npm run build
# 運行測試
npm test
import React, { Component } from 'react';
import { connect } from 'react-redux';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
// 組件狀態(tài)
};
}
componentDidMount() {
// 生命周期方法
}
render() {
return (
// JSX內容
);
}
}
export default connect(mapStateToProps)(MyComponent);
項目使用統一的 fetch 工具進(jìn)行API調用:
// src/utils/fetch.js
export const get = (url, query = {}, options = {}) => {
return ajax({
url: getUrl(url, query),
...options
});
}
export const post = (url, query = {}, data = {}, options = {}) => {
return ajax({
type: 'post',
url: getUrl(url, query),
data,
...options
});
}
所有API接口在 src/utils/zjUrl.js
中統一管理:
const allUrl = {
user: {
login: joint("tms/gg/jk/enterprise/login.do"),
getVersion: joint("tms/gg/jk/userFun/query.do"),
},
ckfpCkgd: {
list: joint("tms/gg/jk/ckfpCkgd/list.do"),
update: joint("tms/gg/jk/ckfpCkgd/update.do"),
}
};
封裝了復雜的表格功能,支持:
// 使用示例
<DynamicTable
id="ckgd"
myKey="entryId"
columns={columns}
dataSource={dataSource}
showSelectionCheckbox={true}
onTableSelect={this.handleTableSelect}
/>
支持動(dòng)態(tài)生成表單字段,包含多種表單控件類(lèi)型:
twinlayout
- 雙欄布局hglayout
- 橫向布局cklayout
- 出口關(guān)單布局// src/utils/router.js
const RouterConfig = () => (
<Router history={hashHistory}>
<Route path="/" component={cklayout}>
<IndexRoute component={Home} />
<Route path="ckgd" component={ckgd} />
</Route>
</Router>
);
// src/utils/store.js
const store = createStore(
rootReducer,
persistedState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(promiseMiddleware)
);
// src/setupProxy.js
module.exports = function(app) {
app.use(proxy('/api', {
target: 'http://localhost:8080',
changeOrigin: true,
}));
};
// 設置token
export const setToken = (token) => ({
type: 'SET_TOKEN',
payload: token
});
// 設置菜單
export const setMenus = (menus) => ({
type: 'SET_MENUS',
payload: menus
});
const userReducer = (state = initialState, action) => {
switch (action.type) {
case 'SET_TOKEN':
return { ...state, token: action.payload };
case 'SET_MENUS':
return { ...state, menus: action.payload };
default:
return state;
}
};
const mapStateToProps = (state) => ({
token: state.user.token,
menus: state.user.menus
});
export default connect(mapStateToProps)(MyComponent);
// 數據格式化
export function dateFormater(milliseconds) {
const date = new Date(milliseconds);
return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`;
}
// 防抖函數
export function debounce(func, wait) {
let timer;
return function (...args) {
const context = this;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// 菜單扁平轉樹(shù)形
export function menuFlatToTree(data) {
const tree = [];
const map = {};
for (const menu of data) {
map[menu.zyDm] = menu;
}
for (const menu of data) {
if (menu.fjdZyDm === 'root') {
tree.push(menu);
} else {
const parent = map[menu.fjdZyDm];
if (parent) {
if (!parent.children) parent.children = [];
parent.children.push(menu);
}
}
}
return tree;
}
class ckgd extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
loading: true,
// 其他狀態(tài)
};
}
// 生命周期方法
componentDidMount() {
this.initData();
}
// 業(yè)務(wù)方法
fetchData = async (page, pageSize, queries) => {
this.setState({ loading: true });
const res = await this.getData(page, pageSize, queries);
if (res) {
this.setState({
dataSource: res.result,
loading: false
});
}
}
// 事件處理
handleTableSelect = (selected) => {
this.setState({ tableSelect: selected });
}
render() {
const { dataSource, loading } = this.state;
return (
<DynamicTable
dataSource={dataSource}
loading={loading}
onTableSelect={this.handleTableSelect}
/>
);
}
}
// 工具函數封裝
export class TableUtils {
static calculateColumnWidth(col, dataSource) {
// 計算列寬度的算法
}
static initializeColumns(columns, fixedColumns, dataSource) {
// 初始化表格列
}
}
// 數據處理函數
export const transformData = (rawData) => {
return rawData.map(item => ({
...item,
formattedDate: dateFormater(item.createTime),
statusText: getStatusText(item.status)
}));
};
// 工具函數
export const getStatusText = (status) => {
const statusMap = {
'Y': '已申報',
'N': '未申報',
'B': '部分申報'
};
return statusMap[status] || status;
};
// 權限控制高階組件
export const withAuth = (WrappedComponent) => {
return class extends Component {
componentDidMount() {
if (!this.props.token) {
this.props.history.push('/login');
}
}
render() {
return this.props.token ? <WrappedComponent {...this.props} /> : null;
}
};
};
const ajax = options => {
options.headers = {
"Content-Type": "application/json;charset=utf-8",
"Authorization": sessionStorage.getItem("token")
};
return new Promise((resolve, reject) => {
$.ajax({
dataType: 'json',
xhrFields: { withCredentials: true },
cache: false,
success: data => resolve(data),
error: ajax => {
if (ajax.status === 401) {
message.warning('您沒(méi)有權限,即將為你跳轉到登錄頁(yè)!');
setTimeout(() => {
window.location.href = allUrl.goHomeUrl;
}, 2000);
}
reject(ajax);
},
...options
});
});
};
// 統一錯誤處理
const handleApiError = (error) => {
if (error.status === 401) {
// 處理未授權
} else if (error.status === 500) {
// 處理服務(wù)器錯誤
} else {
// 其他錯誤
}
};
// 環(huán)境判斷
if (window.location.host === "localhost:3000") {
serverUrl = "http://117.78.0.163:8521/";
} else {
serverUrl = "/";
}
InvoicingDev 項目是一個(gè)功能完整、架構清晰的前端應用,采用了現代化的開(kāi)發(fā)模式和最佳實(shí)踐。項目具有良好的可維護性和擴展性,為后續的功能迭代和性能優(yōu)化奠定了堅實(shí)基礎。