diff --git a/.vscode/launch.json b/.vscode/launch.json
index dd08d4d..cd44ee8 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -45,7 +45,8 @@
"-g postgres://FashionAdmin:123456@10.8.30.36:5432/data_center",
"--redisHost localhost",
"--redisPort 6379",
- "--apiEmisUrl http://10.8.30.161:1111",
+ "--apiEmisUrl http://10.8.30.103:14000", //开发
+ // "--apiEmisUrl http://10.8.30.161:1111", //测试
// 镇江研发结束
// 测试
"--qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5",
diff --git a/api/app/lib/controllers/report/achievement.js b/api/app/lib/controllers/report/achievement.js
index 9a027ef..a5c2e70 100644
--- a/api/app/lib/controllers/report/achievement.js
+++ b/api/app/lib/controllers/report/achievement.js
@@ -257,6 +257,73 @@ async function exportAchievementDetail(ctx, dataList) {
}
}
+async function getReceivedNumbers(ctx) {
+ try {
+ const models = ctx.fs.dc.models;
+ let list = await models.ReceivableDetail.findAll({//查编号
+ attributes: ['number']
+ });
+ ctx.status = 200
+ ctx.body = list;
+ } catch (error) {
+ ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
+ ctx.status = 400;
+ ctx.body = {
+ message: typeof error == 'string' ? error : undefined
+ }
+ }
+}
+
+async function importBackDetails(ctx) {
+ let errorMsg = { message: '导入回款明细失败' };
+ const transaction = await ctx.fs.dc.orm.transaction();
+ try {
+ const models = ctx.fs.dc.models;
+ const data = ctx.request.body;
+ let addArr = [];
+ let dataList = await models.ReceivableDetail.findAll({//查编号
+ attributes: ['number']
+ });
+ data.map(d => {
+ let exist = dataList.find(m => m.dataValues.number == d.number);
+ if (!exist) {
+ addArr.push(d);
+ }
+ })
+ //只处理新增的
+ if (addArr.length) {
+ await models.ReceivableDetail.bulkCreate(addArr);
+ }
+ await transaction.commit();
+ ctx.status = 204;
+ } catch (error) {
+ await transaction.rollback();
+ ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
+ ctx.status = 400;
+ ctx.body = errorMsg;
+ }
+}
+
+async function importAchieveDetails(ctx) {
+ let errorMsg = { message: '导入业绩明细失败' };
+ const transaction = await ctx.fs.dc.orm.transaction();
+ try {
+ const models = ctx.fs.dc.models;
+ const data = ctx.request.body;
+ //业绩明细 没有唯一标识,前端校验后,导啥存啥
+ if (data.length) {
+ await models.PerformanceDetail.bulkCreate(data);
+ }
+ await transaction.commit();
+ ctx.status = 204;
+ } catch (error) {
+ await transaction.rollback();
+ ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
+ ctx.status = 400;
+ ctx.body = errorMsg;
+ }
+}
+
/**
* 查询合同明细表数据
* @param {*} ctx ctx ctx.query:{keywordTarget-关键字项、keyword-关键字内容、limit-页宽, page-页码}
@@ -283,8 +350,99 @@ async function getContractDetail(ctx) {
ctx.body = { name: 'FindError', message: '查询合同明细表数据失败' }
}
}
+
+/**
+ * 查询开票明细表数据
+ * @param {*} ctx ctx ctx.query:{keywordTarget-关键字项、keyword-关键字内容、limit-页宽, page-页码}
+ */
+async function getInvoicingDetail(ctx) {
+ try {
+ const { models } = ctx.fs.dc;
+ const { keywordTarget, keyword, limit, page } = ctx.query;
+ const where = {};
+ if (keywordTarget && keyword) {
+ where[keywordTarget] = { $iLike: `%${keyword}%` };
+ }
+ let invoiceDetail = await models.InvoiceDetail.findAndCountAll({
+ where: where,
+ offset: Number(page) * Number(limit),
+ limit: Number(limit),
+ order: [['id', 'DESC']]
+ });
+ ctx.status = 200
+ ctx.body = invoiceDetail;
+ } catch (error) {
+ ctx.fs.logger.error(`path:${ctx.path},error:${error}`)
+ ctx.status = 400;
+ ctx.body = { name: 'FindError', message: '查询开票明细表数据失败' }
+ }
+}
+
+/**
+ * 导出合同明细表数据
+ */
+async function exportContractDetail(ctx) {
+ try {
+ const { models } = ctx.fs.dc;
+ let exportData = await models.ContractDetail.findAll({
+ order: [['id', 'DESC']]
+ });
+ const { utils: { simpleExcelDown, contractDetailsColumnKeys } } = ctx.app.fs;
+ let header = [];
+ Object.keys(contractDetailsColumnKeys).map(key => {
+ header.push({ title: contractDetailsColumnKeys[key], key: key });
+ })
+ const fileName = `合同明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'
+ const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName, needIndexCell: false })
+ const fileData = fs.readFileSync(filePath);
+ ctx.status = 200;
+ ctx.set('Content-Type', 'application/x-xls');
+ ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
+ ctx.body = fileData;
+ } catch (error) {
+ ctx.fs.logger.error(`path:${ctx.path},error:${error}`)
+ ctx.status = 400;
+ ctx.body = { name: 'ExportAllError', message: '导出合同明细表数据失败' }
+ }
+}
+
+
+/**
+ * 导出开票明细表数据
+ */
+async function exportInvoicingDetail(ctx) {
+ try {
+ const { models } = ctx.fs.dc;
+ let exportData = await models.InvoiceDetail.findAll({
+ order: [['id', 'DESC']]
+ });
+ const { utils: { simpleExcelDown, invoicingDetailsColumnKeys } } = ctx.app.fs;
+ let header = [];
+ Object.keys(invoicingDetailsColumnKeys).map(key => {
+ header.push({ title: invoicingDetailsColumnKeys[key], key: key });
+ })
+ const fileName = `开票明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'
+ const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName, needIndexCell: false })
+ const fileData = fs.readFileSync(filePath);
+ ctx.status = 200;
+ ctx.set('Content-Type', 'application/x-xls');
+ ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
+ ctx.body = fileData;
+ } catch (error) {
+ ctx.fs.logger.error(`path:${ctx.path},error:${error}`)
+ ctx.status = 400;
+ ctx.body = { name: 'ExportAllError', message: '导出开票明细表数据失败' }
+ }
+}
module.exports = {
getReceivedDetail,//回款
getAchievementDetail,//业绩
+
+ getReceivedNumbers,//查询回款明细表 已有的所有编号
+ importBackDetails,//导入回款明细
+ importAchieveDetails,//导入业绩明细
getContractDetail,
+ getInvoicingDetail,
+ exportContractDetail,
+ exportInvoicingDetail
}
\ No newline at end of file
diff --git a/api/app/lib/models/performance_detail.js b/api/app/lib/models/performance_detail.js
index ff431ee..59aa6e1 100644
--- a/api/app/lib/models/performance_detail.js
+++ b/api/app/lib/models/performance_detail.js
@@ -13,11 +13,11 @@ module.exports = dc => {
comment: null,
primaryKey: true,
field: "id",
- autoIncrement: false
+ autoIncrement: true
},
recConDate: {
type: DataTypes.DATEONLY,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "收到合同日期",
primaryKey: false,
@@ -26,7 +26,7 @@ module.exports = dc => {
},
month: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "月份",
primaryKey: false,
@@ -35,7 +35,7 @@ module.exports = dc => {
},
department: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "部门:申请部门",
primaryKey: false,
@@ -44,7 +44,7 @@ module.exports = dc => {
},
sale: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "销售人员",
primaryKey: false,
@@ -53,7 +53,7 @@ module.exports = dc => {
},
customer: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "客户名称:【甲方名称】",
primaryKey: false,
@@ -62,7 +62,7 @@ module.exports = dc => {
},
item: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "项目名称",
primaryKey: false,
@@ -71,7 +71,7 @@ module.exports = dc => {
},
amount: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "合同金额:【合同金额\n(元)】",
primaryKey: false,
@@ -80,7 +80,7 @@ module.exports = dc => {
},
realPerformance: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "实际业绩;(合同金额-预\n支提成及委外费用)*特批折算比例\n(G-L)*K",
primaryKey: false,
@@ -89,7 +89,7 @@ module.exports = dc => {
},
assessmentPerformance: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "合同金额*省外业务(1.1)*复购业务\n(1.05)*可复制的业务路径(1.1)",
primaryKey: false,
@@ -98,7 +98,7 @@ module.exports = dc => {
},
isApproval: {
type: DataTypes.BOOLEAN,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "价格是否特批:根据折算比例-《合同明细表》推\n算(100%——“是”;其他为否)",
primaryKey: false,
@@ -107,7 +107,7 @@ module.exports = dc => {
},
approvalProp: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "特批折算比例:【业绩折\n算比例】",
primaryKey: false,
@@ -116,7 +116,7 @@ module.exports = dc => {
},
cost: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "预支提成及委外费用",
primaryKey: false,
@@ -125,7 +125,7 @@ module.exports = dc => {
},
serviceLine: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "业务线",
primaryKey: false,
@@ -134,7 +134,7 @@ module.exports = dc => {
},
cusType: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "客户类型",
primaryKey: false,
@@ -143,7 +143,7 @@ module.exports = dc => {
},
industry: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "行业",
primaryKey: false,
@@ -152,7 +152,7 @@ module.exports = dc => {
},
source: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "信息来源",
primaryKey: false,
@@ -161,7 +161,7 @@ module.exports = dc => {
},
itemType: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "项目类型",
primaryKey: false,
@@ -170,7 +170,7 @@ module.exports = dc => {
},
cusProvince: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "客户省份",
primaryKey: false,
@@ -179,7 +179,7 @@ module.exports = dc => {
},
cusAttribute: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "客户属性:G:政府、事业单位\nB:央企、国企、平台商\nC:资源方",
primaryKey: false,
@@ -188,7 +188,7 @@ module.exports = dc => {
},
repurchaseCount: {
type: DataTypes.INTEGER,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "复购次数:同一客户储备成单-成单项目数量\n(PM)",
primaryKey: false,
@@ -197,7 +197,7 @@ module.exports = dc => {
},
reproducible: {
type: DataTypes.BOOLEAN,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "是否可复制的业务路径:《合同评审》表单获取",
primaryKey: false,
@@ -206,7 +206,7 @@ module.exports = dc => {
},
outProvince: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "省外业务1.1:当【客户省份】=“江西”时,省外业务=1;当【客户省份】<>“江西”时,省外业务=1.1",
primaryKey: false,
@@ -215,7 +215,7 @@ module.exports = dc => {
},
repurchase: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "复购业务1.05:当【复购次数】\n<2时,复购业务=1;当【复购次数】\n>=2时,复购业务=1.05;",
primaryKey: false,
@@ -224,7 +224,7 @@ module.exports = dc => {
},
isreproduce: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "可复制的业务路径1.1:当【是否可复\n制的业务路径】=“是”,可复制的业务路径=1.1;当【是否可复制的业务路径】=“否”,可复制的业务路径=1;",
primaryKey: false,
diff --git a/api/app/lib/models/receivable_detail.js b/api/app/lib/models/receivable_detail.js
index bd9faa0..3d9c30b 100644
--- a/api/app/lib/models/receivable_detail.js
+++ b/api/app/lib/models/receivable_detail.js
@@ -13,7 +13,7 @@ module.exports = dc => {
comment: null,
primaryKey: true,
field: "id",
- autoIncrement: false
+ autoIncrement: true
},
year: {
type: DataTypes.STRING,
@@ -44,7 +44,7 @@ module.exports = dc => {
},
department: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "部门:申请部门",
primaryKey: false,
@@ -53,7 +53,7 @@ module.exports = dc => {
},
sale: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "销售人员:申请人",
primaryKey: false,
@@ -62,7 +62,7 @@ module.exports = dc => {
},
contractNo: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "合同编号",
primaryKey: false,
@@ -71,7 +71,7 @@ module.exports = dc => {
},
customer: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "客户名称:【甲方名称】",
primaryKey: false,
@@ -80,7 +80,7 @@ module.exports = dc => {
},
item: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "项目名称",
primaryKey: false,
@@ -89,7 +89,7 @@ module.exports = dc => {
},
amount: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "合同金额:【合同金额\n(元)】",
primaryKey: false,
@@ -98,7 +98,7 @@ module.exports = dc => {
},
changeAmount: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "变更后合同金额",
primaryKey: false,
@@ -107,7 +107,7 @@ module.exports = dc => {
},
receivableYear: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "回款年份",
primaryKey: false,
@@ -116,7 +116,7 @@ module.exports = dc => {
},
receivableDate: {
type: DataTypes.DATEONLY,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "回款日期",
primaryKey: false,
@@ -125,7 +125,7 @@ module.exports = dc => {
},
receivableAmount: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "回款金额",
primaryKey: false,
@@ -134,7 +134,7 @@ module.exports = dc => {
},
invoicedBack: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "开票-回款",
primaryKey: false,
@@ -143,7 +143,7 @@ module.exports = dc => {
},
remainConAmount: {
type: DataTypes.STRING,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "剩余合同金额",
primaryKey: false,
@@ -152,7 +152,7 @@ module.exports = dc => {
},
incomeConfirmdate: {
type: DataTypes.DATEONLY,
- allowNull: false,
+ allowNull: true,
defaultValue: null,
comment: "收入确认时间",
primaryKey: false,
diff --git a/api/app/lib/routes/report/index.js b/api/app/lib/routes/report/index.js
index b986cd6..aedc624 100644
--- a/api/app/lib/routes/report/index.js
+++ b/api/app/lib/routes/report/index.js
@@ -16,6 +16,26 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/detail/achievement'] = { content: '查询业绩明细表', visible: false };
router.get('/detail/achievement', achieve.getAchievementDetail);
+
+
+
+ app.fs.api.logAttr['GET/detail/received/numbers'] = { content: '查询业绩明细已有的编号集合', visible: false };
+ router.get('/detail/received/numbers', achieve.getReceivedNumbers);
+
+ app.fs.api.logAttr['POST/add/received/back/bulk'] = { content: '导入回款明细', visible: true };
+ router.post('/add/received/back/bulk', achieve.importBackDetails);
+
+ app.fs.api.logAttr['POST/add/achievement/bulk'] = { content: '导入业绩明细', visible: true };
+ router.post('/add/achievement/bulk', achieve.importAchieveDetails);
app.fs.api.logAttr['GET/contract/detail'] = { content: '查询合同明细表', visible: false };
router.get('/contract/detail', achieve.getContractDetail);
+
+ app.fs.api.logAttr['GET/invoicing/detail'] = { content: '查询开票明细表', visible: false };
+ router.get('/invoicing/detail', achieve.getInvoicingDetail);
+
+ app.fs.api.logAttr['GET/export/contract/detail'] = { content: '导出合同明细表', visible: false };
+ router.get('/export/contract/detail', achieve.exportContractDetail);
+
+ app.fs.api.logAttr['GET/export/invoicing/detail'] = { content: '导出开票明细表', visible: false };
+ router.get('/export/invoicing/detail', achieve.exportInvoicingDetail);
};
\ No newline at end of file
diff --git a/api/app/lib/utils/xlsxDownload.js b/api/app/lib/utils/xlsxDownload.js
index 1aa03e7..c52d445 100644
--- a/api/app/lib/utils/xlsxDownload.js
+++ b/api/app/lib/utils/xlsxDownload.js
@@ -19,7 +19,7 @@ module.exports = function (app, opts) {
}
}
- async function simpleExcelDown({ data = [], header = [], fileName = moment().format('YYYY-MM-DD HH:mm:ss') } = {}) {
+ async function simpleExcelDown({ data = [], header = [], fileName = moment().format('YYYY-MM-DD HH:mm:ss'), needIndexCell = true } = {}) {
const fileDirPath = path.join(__dirname, `../../downloadFiles`)
makeDir(fileDirPath)
const file = new xlsx.File();
@@ -35,9 +35,11 @@ module.exports = function (app, opts) {
headerStyle.border.bottomColor = '#000000';
const headerRow = sheet_1.addRow();
- const indexCell = headerRow.addCell();
- indexCell.value = '序号'
- indexCell.style = headerStyle
+ if (needIndexCell) {
+ const indexCell = headerRow.addCell();
+ indexCell.value = '序号'
+ indexCell.style = headerStyle
+ }
for (let h of header) {
const cell = headerRow.addCell();
cell.value = h.title;
@@ -54,9 +56,11 @@ module.exports = function (app, opts) {
style.border.bottomColor = '#000000';
for (let i = 0; i < data.length; i++) {
const row = sheet_1.addRow();
- const indexCell = row.addCell();
- indexCell.value = i + 1
- indexCell.style = headerStyle
+ if (needIndexCell) {
+ const indexCell = row.addCell();
+ indexCell.value = i + 1
+ indexCell.style = headerStyle
+ }
for (let h of header) {
const cell = row.addCell();
cell.value = data[i][h.key] || h.defaultValue || '-';
diff --git a/web/client/src/layout/actions/global.js b/web/client/src/layout/actions/global.js
index 4d019aa..4bbf599 100644
--- a/web/client/src/layout/actions/global.js
+++ b/web/client/src/layout/actions/global.js
@@ -38,6 +38,7 @@ export function initApiRoot () {
type: INIT_API_ROOT,
payload: {
apiRoot: res.root,
+ webPepUrl:res.webPepUrl
}
})
});
diff --git a/web/client/src/layout/containers/layout/index.jsx b/web/client/src/layout/containers/layout/index.jsx
index 9c7eb26..31a1186 100644
--- a/web/client/src/layout/containers/layout/index.jsx
+++ b/web/client/src/layout/containers/layout/index.jsx
@@ -10,9 +10,13 @@ import { resize } from '../../actions/global';
import * as NProgress from 'nprogress';
import PerfectScrollbar from 'perfect-scrollbar';
import { useLocation } from "react-router";
+import { RouteTable } from '$utils';
+import { RouteRequest } from '@peace/utils';
+import Cookie from 'js-cookie';
+import { login, LOGIN_SUCCESS } from '../../../sections/auth/actions/auth';
NProgress.configure({
- template: `
+ template: `
@@ -23,303 +27,337 @@ NProgress.configure({
});
let scrollbar
+let requestUser = true
+let requestlogout = false;
+
// const location111 = useLocation();
const LayoutContainer = props => {
- const {
- dispatch, msg, user, copyright, children, sections, clientWidth, clientHeight,
- location, match, routes, history, socket,
- } = props
- const [collapsed, setCollapsed] = useState(false)
+ const {
+ dispatch, actions, msg, user, copyright, children, sections, clientWidth, clientHeight,
+ location, match, routes, history, socket, apiRoot, webPepUrl
+ } = props
+ const [collapsed, setCollapsed] = useState(false)
- NProgress.start();
+ NProgress.start();
- const resize_ = () => {
- dispatch(resize(
- document.getElementById('DrApp').clientHeight,
- document.getElementById('DrApp').clientWidth - (collapsed ? 120 : 240)
- ));
- }
- function deepCopy(data) {//深拷贝
- //string,number,bool,null,undefined,symbol
- //object,array,date
- if (data && typeof data === "object") {
- //针对函数的拷贝
- if (typeof data === "function") {
- let tempFunc = data.bind(null);
- tempFunc.prototype = deepCopy(data.prototype);
- return tempFunc;
- }
+ const resize_ = () => {
+ dispatch(resize(
+ document.getElementById('DrApp').clientHeight,
+ document.getElementById('DrApp').clientWidth - (collapsed ? 120 : 240)
+ ));
+ }
+ function deepCopy (data) {//深拷贝
+ //string,number,bool,null,undefined,symbol
+ //object,array,date
+ if (data && typeof data === "object") {
+ //针对函数的拷贝
+ if (typeof data === "function") {
+ let tempFunc = data.bind(null);
+ tempFunc.prototype = deepCopy(data.prototype);
+ return tempFunc;
+ }
- switch (Object.prototype.toString.call(data)) {
- case "[object String]":
- return data.toString();
- case "[object Number]":
- return Number(data.toString());
- case "[object Boolean]":
- return new Boolean(data.toString());
- case "[object Date]":
- return new Date(data.getTime());
- case "[object Array]":
- var arr = [];
- for (let i = 0; i < data.length; i++) {
- arr[i] = deepCopy(data[i]);
- }
- return arr;
+ switch (Object.prototype.toString.call(data)) {
+ case "[object String]":
+ return data.toString();
+ case "[object Number]":
+ return Number(data.toString());
+ case "[object Boolean]":
+ return new Boolean(data.toString());
+ case "[object Date]":
+ return new Date(data.getTime());
+ case "[object Array]":
+ var arr = [];
+ for (let i = 0; i < data.length; i++) {
+ arr[i] = deepCopy(data[i]);
+ }
+ return arr;
- //js自带对象或用户自定义类实例
- case "[object Object]":
- var obj = {};
- for (let key in data) {
- //会遍历原型链上的属性方法,可以用hasOwnProperty来控制 (obj.hasOwnProperty(prop)
- obj[key] = deepCopy(data[key]);
- }
- return obj;
- }
- } else {
- //string,number,bool,null,undefined,symbol
- return data;
- }
- }
- const [allItems, setAllItems] = useState([])
- // const [headerItems, setHeaderItems] = useState([])
- const [leftItems, setLeftItems] = useState([])
- const [leftChange, setLeftChange] = useState(true)
- const [leftShow, setLeftShow] = useState(false)
- useEffect(() => {
- let topItems = []//头部导航
- const lastSelectedKeys = localStorage.getItem('poms_selected_sider')
- let nextItems = []//所有导航
- for (let c of sections) {
- if (typeof c.getNavItem == 'function') {
- let item = c.getNavItem(user, dispatch);
- if (item) {
- nextItems.push.apply(nextItems, item)
- if (item.length > 0) {
- for (let j = 0; j < item.length; j++) {
- let itm = deepCopy(item[j]);
- if (itm.hasOwnProperty('items')) {
- for (let i = 0; i < itm.items.length; i++) {
- itm.items[i].fatherKey = itm.itemKey
- delete itm.items[i].items
- }
- // topItems.push(itm)
- // }
- // else {
- // topItems.push.apply(topItems, item)
- }
+ //js自带对象或用户自定义类实例
+ case "[object Object]":
+ var obj = {};
+ for (let key in data) {
+ //会遍历原型链上的属性方法,可以用hasOwnProperty来控制 (obj.hasOwnProperty(prop)
+ obj[key] = deepCopy(data[key]);
+ }
+ return obj;
+ }
+ } else {
+ //string,number,bool,null,undefined,symbol
+ return data;
+ }
+ }
+ const [allItems, setAllItems] = useState([])
+ // const [headerItems, setHeaderItems] = useState([])
+ const [leftItems, setLeftItems] = useState([])
+ const [leftChange, setLeftChange] = useState(true)
+ const [leftShow, setLeftShow] = useState(false)
+ useEffect(() => {
+ let topItems = []//头部导航
+ const lastSelectedKeys = localStorage.getItem('poms_selected_sider')
+ let nextItems = []//所有导航
+ for (let c of sections) {
+ if (typeof c.getNavItem == 'function') {
+ let item = c.getNavItem(user, dispatch);
+ if (item) {
+ nextItems.push.apply(nextItems, item)
+ if (item.length > 0) {
+ for (let j = 0; j < item.length; j++) {
+ let itm = deepCopy(item[j]);
+ if (itm.hasOwnProperty('items')) {
+ for (let i = 0; i < itm.items.length; i++) {
+ itm.items[i].fatherKey = itm.itemKey
+ delete itm.items[i].items
}
- }
- }
+ // topItems.push(itm)
+ // }
+ // else {
+ // topItems.push.apply(topItems, item)
+ }
+ }
+ }
}
- }
- setAllItems(nextItems)
- // setHeaderItems(topItems)
- if (lastSelectedKeys) {//如果有缓存
- for (let i = 0; i < nextItems.length; i++) {
- if (JSON.parse(lastSelectedKeys)[0] == nextItems[i].itemKey) {
+ }
+ }
+ setAllItems(nextItems)
+ // setHeaderItems(topItems)
+ if (lastSelectedKeys) {//如果有缓存
+ for (let i = 0; i < nextItems.length; i++) {
+ if (JSON.parse(lastSelectedKeys)[0] == nextItems[i].itemKey) {
- // let openArr = []
- // for (let j = 0; j < nextItems[i].items.length; j++) {
- // openArr.push(nextItems[i].items[j].itemKey)
- // }
- // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
- setLeftItems(nextItems[i].items)
- }
+ // let openArr = []
+ // for (let j = 0; j < nextItems[i].items.length; j++) {
+ // openArr.push(nextItems[i].items[j].itemKey)
+ // }
+ // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
+ setLeftItems(nextItems[i].items)
}
- setLeftShow(true)
- }
- else {
- setLeftShow(false)
- }
+ }
+ setLeftShow(true)
+ }
+ else {
+ setLeftShow(false)
+ }
- window.addEventListener('resize', resize_);
- return () => {
- window.removeEventListener('resize', resize_);
- }
- }, [])
+ window.addEventListener('resize', resize_);
+ return () => {
+ window.removeEventListener('resize', resize_);
+ }
+ }, [])
- useEffect(() => {
- let pathnameArr = location.pathname.split('/')
- let openArr = []
- for (let i = 0; i < allItems.length; i++) {
- if (allItems[i].items) {
- for (let j = 0; j < allItems[i].items.length; j++) {
- if (allItems[i].items[j].items) {
- for (let k = 0; k < allItems[i].items[j].items.length; k++) {
- if (allItems[i].items[j].items[k].to == location.pathname) {
- for (let o = 0; o < allItems[i].items.length; o++) {
- openArr.push(allItems[i].items[o].itemKey)
- }
- localStorage.setItem('poms_selected_sider', JSON.stringify([pathnameArr[1]]))
- // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
- setLeftItems(allItems[i].items)
- setLeftShow(true)
- }
+ useEffect(() => {
+ let pathnameArr = location.pathname.split('/')
+ let openArr = []
+ for (let i = 0; i < allItems.length; i++) {
+ if (allItems[i].items) {
+ for (let j = 0; j < allItems[i].items.length; j++) {
+ if (allItems[i].items[j].items) {
+ for (let k = 0; k < allItems[i].items[j].items.length; k++) {
+ if (allItems[i].items[j].items[k].to == location.pathname) {
+ for (let o = 0; o < allItems[i].items.length; o++) {
+ openArr.push(allItems[i].items[o].itemKey)
}
- }
- }
+ localStorage.setItem('poms_selected_sider', JSON.stringify([pathnameArr[1]]))
+ // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
+ setLeftItems(allItems[i].items)
+ setLeftShow(true)
+ }
+ }
+ }
}
- }
- }, [location])
+ }
+ }
+ }, [location])
- useEffect(() => {
- NProgress.done();
- if ((!user || !user.authorized)) {
- history.push('/signin');
- }
- if (msg) {
- if (msg.done) {
- Notification.success({
- // title: msg.done,
- content: msg.done,
- duration: 2,
- })
- }
- if (msg.error) {
- Notification.error({
- // title: msg.error,
- content: msg.error,
- duration: 2,
- })
- }
- }
- const dom = document.getElementById('page-content');
- if (dom) {
- if (!scrollbar) {
- scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true });
- scrollbar.update();
- } else {
- scrollbar.update();
- dom.scrollTop = 0;
- }
- }
- })
+ useEffect(() => {
+ NProgress.done();
+ if ((!user || !user.authorized)) {
+ // history.push('/signin');
+ getUserInfoByToken()
+ }
+ if (msg) {
+ if (msg.done) {
+ Notification.success({
+ // title: msg.done,
+ content: msg.done,
+ duration: 2,
+ })
+ }
+ if (msg.error) {
+ Notification.error({
+ // title: msg.error,
+ content: msg.error,
+ duration: 2,
+ })
+ }
+ }
+ const dom = document.getElementById('page-content');
+ if (dom) {
+ if (!scrollbar) {
+ scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true });
+ scrollbar.update();
+ } else {
+ scrollbar.update();
+ dom.scrollTop = 0;
+ }
+ }
+ })
+
+ const getUserInfoByToken = () => {
+ if (requestUser) {
+ requestUser = false;
+ RouteRequest.get(RouteTable.apiRoot).then(res => {
+ let token = Cookie.get('pepToken', { domain: res.domain });
+ dispatch(login({ token })).then(res => {
+ console.log(res);
+ if (res.type == 'LOGIN_SUCCESS') {
+ const data = res.payload?.user || {}
+ history.push('/businessManagement/pmReport/reserveItemsReporting')
+ localStorage.setItem('poms_open_sider', JSON.stringify(["pmReport"]))
+ localStorage.setItem('poms_selected_sider', JSON.stringify(["businessManagement"]))
+ localStorage.setItem('word', JSON.stringify('')) //系统登录代码时候存的,不清楚意义
+ dispatch(actions.layout.initWebSocket({ ioUrl: apiRoot, token: data.token, hrUserId: data.hrUserInfo && hrUserInfo.id }))
+ } else {
+ window.location.href = `${webPepUrl}/signin`
+ }
+ }, error => {
+ window.location.href = `${webPepUrl}/signin`
+ })
+ }, error => {
+ message.error('鉴权失败', 5);
+ window.location.href = `${webPepUrl}/signin`
+ })
+ }
+ }
- // websocket 使用测试
- useEffect(() => {
- // console.log(socket)
- if (socket) {
- socket.on('CAMERA_ONLINE', function (msg) {
- console.info(msg);
- if (msg.online == 'ON') {
- Notification.success({
- title: 'Hi',
- content: (),
- duration: 2,
- })
- }
- if (msg.online == 'OFF') {
- Notification.error({
- title: 'Hi',
- content: (),
- duration: 2,
- })
- }
- });
- return () => {
- socket.off("CAMERA_ONLINE");
- }
- }
- }, [socket])
- return (
-
- {
- <>
-
- {
- setCollapsed(!collapsed);
- }}
- collapsed={collapsed}
- history={history}
- tochange={(val) => {
- // setLeftChange(!leftChange) //会导致顶部右上角转小圈
- if (val.fatherKey) {
- localStorage.setItem('poms_selected_sider', JSON.stringify([val.itemKey]));
- const historyOpenKeys = localStorage.getItem('poms_open_sider');
- const openKeys = historyOpenKeys && JSON.parse(historyOpenKeys).concat(val.openKey || val.itemKey) || [val.openKey || val.itemKey]
- localStorage.setItem('poms_open_sider', JSON.stringify(openKeys));
- // for (let i = 0; i < allItems.length; i++) {
- // if (val.fatherKey == allItems[i].itemKey) {
- // let openArr = []
- // for (let j = 0; j < allItems[i].items.length; j++) {
- // openArr.push(allItems[i].items[j].itemKey)
- // }
- // localStorage.setItem('poms_selected_sider', JSON.stringify([val.fatherKey]))
- // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
- // setLeftItems(allItems[i].items)
- // }
- // }
- // setLeftShow(true)
- }
- else {
- localStorage.setItem('poms_open_sider', JSON.stringify([]))
- localStorage.removeItem('poms_selected_sider')
- // setLeftShow(false)
- }
- history.push(val.to);
- }}
- />
-
-
- {leftShow ? (
-
- ) : ('')}
-
-
-
-
- >
+ // websocket 使用测试
+ useEffect(() => {
+ // console.log(socket)
+ if (socket) {
+ socket.on('CAMERA_ONLINE', function (msg) {
+ console.info(msg);
+ if (msg.online == 'ON') {
+ Notification.success({
+ title: 'Hi',
+ content: (),
+ duration: 2,
+ })
+ }
+ if (msg.online == 'OFF') {
+ Notification.error({
+ title: 'Hi',
+ content: (),
+ duration: 2,
+ })
}
-
- )
+ });
+ return () => {
+ socket.off("CAMERA_ONLINE");
+ }
+ }
+ }, [socket])
+
+ return (
+
+ {
+ <>
+
+ {
+ setCollapsed(!collapsed);
+ }}
+ collapsed={collapsed}
+ history={history}
+ tochange={(val) => {
+ // setLeftChange(!leftChange) //会导致顶部右上角转小圈
+ if (val.fatherKey) {
+ localStorage.setItem('poms_selected_sider', JSON.stringify([val.itemKey]));
+ const historyOpenKeys = localStorage.getItem('poms_open_sider');
+ const openKeys = historyOpenKeys && JSON.parse(historyOpenKeys).concat(val.openKey || val.itemKey) || [val.openKey || val.itemKey]
+ localStorage.setItem('poms_open_sider', JSON.stringify(openKeys));
+ // for (let i = 0; i < allItems.length; i++) {
+ // if (val.fatherKey == allItems[i].itemKey) {
+ // let openArr = []
+ // for (let j = 0; j < allItems[i].items.length; j++) {
+ // openArr.push(allItems[i].items[j].itemKey)
+ // }
+ // localStorage.setItem('poms_selected_sider', JSON.stringify([val.fatherKey]))
+ // localStorage.setItem('poms_open_sider', JSON.stringify(openArr))
+ // setLeftItems(allItems[i].items)
+ // }
+ // }
+ // setLeftShow(true)
+ }
+ else {
+ localStorage.setItem('poms_open_sider', JSON.stringify([]))
+ localStorage.removeItem('poms_selected_sider')
+ // setLeftShow(false)
+ }
+ history.push(val.to);
+ }}
+ />
+
+
+ {leftShow ? (
+
+ ) : ('')}
+
+
+
+
+ >
+ }
+
+ )
}
-function mapStateToProps(state) {
- const { global, auth, ajaxResponse, webSocket } = state;
- return {
- title: global.title,
- copyright: global.copyright,
- sections: global.sections,
- actions: global.actions,
- clientWidth: global.clientWidth,
- clientHeight: global.clientHeight,
- msg: ajaxResponse.msg,
- user: auth.user,
- socket: webSocket.socket
- };
+function mapStateToProps (state) {
+ const { global, auth, ajaxResponse, webSocket } = state;
+ return {
+ title: global.title,
+ copyright: global.copyright,
+ sections: global.sections,
+ actions: global.actions,
+ clientWidth: global.clientWidth,
+ clientHeight: global.clientHeight,
+ msg: ajaxResponse.msg,
+ user: auth.user,
+ socket: webSocket.socket,
+ apiRoot: global.apiRoot,
+ webPepUrl: global.webPepUrl,
+ };
}
export default connect(mapStateToProps)(LayoutContainer);
\ No newline at end of file
diff --git a/web/client/src/layout/reducers/global.js b/web/client/src/layout/reducers/global.js
index 8187eb7..4f52077 100644
--- a/web/client/src/layout/reducers/global.js
+++ b/web/client/src/layout/reducers/global.js
@@ -11,6 +11,7 @@ function global (state = {
clientHeight: 768,
clientWidth: 1024,
apiRoot: '',
+ webPepUrl: ''
}, action) {
const payload = action.payload;
switch (action.type) {
@@ -30,6 +31,7 @@ function global (state = {
case INIT_API_ROOT:
return Immutable.fromJS(state).merge({
apiRoot: payload.apiRoot,
+ webPepUrl: payload.webPepUrl
}).toJS();
default:
return state;
diff --git a/web/client/src/sections/auth/actions/auth.js b/web/client/src/sections/auth/actions/auth.js
index afcb759..18a1e59 100644
--- a/web/client/src/sections/auth/actions/auth.js
+++ b/web/client/src/sections/auth/actions/auth.js
@@ -4,7 +4,7 @@ import { ApiTable, AxyRequest, EmisRequest } from '$utils'
import { Request } from '@peace/utils';
export const INIT_AUTH = 'INIT_AUTH';
-export function initAuth(userData) {
+export function initAuth (userData) {
const sessionUser = JSON.parse(sessionStorage.getItem('dcUser'))
const user = userData || sessionUser || {};
if (user.authorized && !sessionUser) {
@@ -21,11 +21,11 @@ export function initAuth(userData) {
export const REQUEST_LOGIN = 'REQUEST_LOGIN';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
-export function login(username, password) {
+export function login ({ username, password, token }) {
return dispatch => {
dispatch({ type: REQUEST_LOGIN });
- if (!username || !password) {
+ if ((!username || !password) && !token) {
dispatch({
type: LOGIN_ERROR,
payload: { error: '请输入账号名和密码' }
@@ -43,7 +43,7 @@ export function login(username, password) {
// },
// });
- return Request.post(ApiTable.login, { username, password, code: 'HR' })
+ return Request.post(ApiTable.login, { username, password, token, code: 'HR' })
.then(user => {
sessionStorage.setItem('dcUser', JSON.stringify(user));
return dispatch({
@@ -63,7 +63,7 @@ export function login(username, password) {
}
export const LOGOUT = 'LOGOUT';
-export function logout() {
+export function logout () {
const user = JSON.parse(sessionStorage.getItem('dcUser'))
user && user.token ?
Request.put(ApiTable.logout, {
diff --git a/web/client/src/sections/business/actions/achievement-report.js b/web/client/src/sections/business/actions/achievement-report.js
index b7d5e94..66c27f3 100644
--- a/web/client/src/sections/business/actions/achievement-report.js
+++ b/web/client/src/sections/business/actions/achievement-report.js
@@ -28,6 +28,40 @@ export function getAchievementDetail(query) {
});
}
+//回款明细 编号集合
+export function getReceivedNumbers() {
+ return (dispatch) => basicAction({
+ type: "get",
+ dispatch: dispatch,
+ actionType: "GET_RECEIVED_DETAIL_NUMBERS",
+ url: `${ApiTable.getReceivedNumbers}`,
+ msg: { option: "查询回款明细编号集合" },
+ reducer: { name: "ReceivedDetailNumbers", params: { noClear: true } },
+ });
+}
+//导入回款明细
+export function importBackDetails(values) {
+ return dispatch => basicAction({
+ type: 'post',
+ dispatch: dispatch,
+ actionType: 'RECEIVED_DETAIL_BULK_ADD',
+ url: ApiTable.importBackDetails,
+ data: values,
+ msg: { option: '导入回款明细' },
+ });
+}
+
+//导入业绩明细
+export function importAchieveDetails(values) {
+ return dispatch => basicAction({
+ type: 'post',
+ dispatch: dispatch,
+ actionType: 'ACHIEVEMENT_DETAIL_BULK_ADD',
+ url: ApiTable.importAchieveDetails,
+ data: values,
+ msg: { option: '导入业绩明细' },
+ });
+}
//查询合同明细表
export function getContractDetail(query) {
return (dispatch) => basicAction({
@@ -39,4 +73,17 @@ export function getContractDetail(query) {
msg: { option: "查询合同明细表" },
reducer: { name: "ContractDetail", params: { noClear: true } },
});
+}
+
+//查询开票明细表
+export function getInvoicingDetail(query) {
+ return (dispatch) => basicAction({
+ type: "get",
+ dispatch: dispatch,
+ actionType: "GET_INVOICING_DETAIL",
+ query: query,
+ url: `${ApiTable.getInvoicingDetail}`,
+ msg: { option: "查询开票明细表" },
+ reducer: { name: "InvoicingDetail", params: { noClear: true } },
+ });
}
\ No newline at end of file
diff --git a/web/client/src/sections/business/constants/index.js b/web/client/src/sections/business/constants/index.js
index 55ebc45..4cf96c3 100644
--- a/web/client/src/sections/business/constants/index.js
+++ b/web/client/src/sections/business/constants/index.js
@@ -71,4 +71,52 @@ export const invoicingDetailsColumnKeys = {
taxRate13: '税率13%',
taxRate9: '税率9%',
taxRate6: '税率6%'
+}
+
+export const backMoneyColumnKeys = {
+ year: '年度',
+ serialNo: '序号',
+ number: '编号',
+ department: '部门',
+ sale: '销售人员',
+ contractNo: '合同编号',
+ customer: '客户名称',
+ item: '项目名称',
+ amount: '合同金额',
+ changeAmount: '变更后合同金额',
+ receivableYear: '回款年份',
+ receivableDate: '回款日期',
+ receivableAmount: '回款金额',
+ invoicedBack: '开票-回款',
+ remainConAmount: '剩余合同金额',
+ incomeConfirmdate: '收入确认时间',
+ thirdPayment: '第三方付款单位',
+ remark: '备注',
+}
+
+export const achievementColumnKeys = {
+ recConDate: '收到合同日期',
+ month: '月份',
+ department: '部门',
+ sale: '销售人员',
+ customer: '客户名称',
+ item: '项目名称',
+ amount: '合同金额',
+ realPerformance: '实际业绩',
+ assessmentPerformance: '考核业绩',
+ isApproval: '价格是否特批',
+ approvalProp: '特批折算比例',
+ cost: '预支提成及委外费用',
+ serviceLine: '业务线',
+ cusType: '客户类型',
+ industry: '行业',
+ source: '信息来源',
+ itemType: '项目类型',
+ cusProvince: '客户省份',
+ cusAttribute: '客户属性',
+ repurchaseCount: '复购次数',
+ reproducible: '是否可复制的业务路径',
+ outProvince: '省外业务1.1',
+ repurchase: '复购业务1.05',
+ isreproduce: '可复制的业务路径1.1',
}
\ No newline at end of file
diff --git a/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx b/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx
index 51e8039..c27d7a9 100644
--- a/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx
+++ b/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx
@@ -4,6 +4,8 @@ import moment from 'moment'
import { Select, Input, Button, Toast, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen } from "$components";
+import { achievementColumnKeys } from '../../constants/index';
+import ImportAchieveModal from './importAchieveModal'
import '../../style.less';
const AchievementDetails = (props) => {
@@ -14,7 +16,8 @@ const AchievementDetails = (props) => {
const [limits, setLimits] = useState()//每页实际条数
const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息
const [tableData, setTableData] = useState([]);
- const [exportUrl, setExportUrl] = useState('')
+ const [importModalV, setImportModalV] = useState(false);
+ const [exportUrl, setExportUrl] = useState('');
const page = useRef(query.page);
function seachValueChange(value) {
setKeyword(value)
@@ -37,151 +40,22 @@ const AchievementDetails = (props) => {
})
}
- const columns = [{
- title: '收到合同日期',
- dataIndex: 'recConDate',
- key: 'recConDate',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '月份',
- dataIndex: 'month',
- key: 'month',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '部门',
- dataIndex: 'department',
- key: 'department',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '销售人员',
- dataIndex: 'sale',
- key: 'sale',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '客户名称',
- dataIndex: 'customer',
- key: 'customer',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '项目名称',
- dataIndex: 'item',
- key: 'item',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '合同金额',
- dataIndex: 'amount',
- key: 'amount',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '实际业绩',
- dataIndex: 'realPerformance',
- key: 'realPerformance',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '考核业绩',
- dataIndex: 'assessmentPerformance',
- key: 'assessmentPerformance',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '价格是否特批',
- dataIndex: 'isApproval',
- key: 'isApproval',
- width: 120,
- render: (text, record) => {text ? '是' : '否'}
- }, {
- title: '特批折算比例',
- dataIndex: 'approvalProp',
- key: 'approvalProp',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '预支提成及委外费用',
- dataIndex: 'cost',
- key: 'cost',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '业务线',
- dataIndex: 'serviceLine',
- key: 'serviceLine',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '客户类型',
- dataIndex: 'cusType',
- key: 'cusType',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '行业',
- dataIndex: 'industry',
- key: 'industry',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '信息来源',
- dataIndex: 'source',
- key: 'source',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '项目类型',
- dataIndex: 'itemType',
- key: 'itemType',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '客户省份',
- dataIndex: 'cusProvince',
- key: 'cusProvince',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '客户属性',
- dataIndex: 'cusAttribute',
- key: 'cusAttribute',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '复购次数',
- dataIndex: 'repurchaseCount',
- key: 'repurchaseCount',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '是否可复制的业务路径',
- dataIndex: 'reproducible',
- key: 'reproducible',
- width: 120,
- render: (text, record) => {text ? '是' : '否'}
- }, {
- title: '省外业务1.1',
- dataIndex: 'outProvince',
- key: 'outProvince',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '复购业务1.05',
- dataIndex: 'repurchase',
- key: 'repurchase',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '可复制的业务路径1.1',
- dataIndex: 'isreproduce',
- key: 'isreproduce',
- width: 120,
- render: (text, record) => {text || '-'}
- }]
+ const renderColumns = (columnKeys) => {
+ let columns = [];
+ Object.keys(columnKeys).map(key => {
+ switch (key) {
+ default:
+ columns.push({
+ title: columnKeys[key], dataIndex: key, key: key, width: 130,
+ render: (text, record) => ['isApproval', 'reproducible'].indexOf(key) != -1 ? JSON.stringify(text) === 'null' ? '-' : text ? '是' : '否'
+ : text === 0 ? text : text ? text : '-'
+ });
+ break;
+ }
+ })
+ return columns;
+ }
+
function handleRow(record, index) {// 给偶数行设置斑马纹
if (index % 2 === 0) {
return {
@@ -273,7 +147,7 @@ const AchievementDetails = (props) => {
placeholder={SkeletonScreen()}
>
{
+ {
+ importModalV ? {
+ setImportModalV(false);
+ getAchievementDetails();
+ }} /> : ''
+ }
{
exportUrl ? : ''
}
diff --git a/web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx b/web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx
index 87678df..bd516ec 100644
--- a/web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx
+++ b/web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx
@@ -4,8 +4,9 @@ import moment from 'moment'
import { Select, Input, Button, Toast, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen } from "$components";
+import ImportBackModal from './importBackModal';
import '../../style.less';
-
+import { backMoneyColumnKeys } from '../../constants/index';
const BackMoneyDetails = (props) => {
const { dispatch, actions, user } = props
const { businessManagement } = actions
@@ -14,7 +15,8 @@ const BackMoneyDetails = (props) => {
const [limits, setLimits] = useState()//每页实际条数
const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息
const [tableData, setTableData] = useState([]);
- const [exportUrl, setExportUrl] = useState('')
+ const [importModalV, setImportModalV] = useState(false);
+ const [exportUrl, setExportUrl] = useState('');
const page = useRef(query.page);
function seachValueChange(value) {
setKeyword(value)
@@ -37,115 +39,21 @@ const BackMoneyDetails = (props) => {
})
}
- const columns = [{
- title: '年度',
- dataIndex: 'year',
- key: 'year',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '序号',
- dataIndex: 'serialNo',
- key: 'serialNo',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '编号',
- dataIndex: 'number',
- key: 'number',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '部门',
- dataIndex: 'department',
- key: 'department',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '销售人员',
- dataIndex: 'sale',
- key: 'sale',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '合同编号',
- dataIndex: 'contractNo',
- key: 'contractNo',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '客户名称',
- dataIndex: 'customer',
- key: 'customer',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '项目名称',
- dataIndex: 'item',
- key: 'item',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '合同金额',
- dataIndex: 'amount',
- key: 'amount',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '变更后合同金额',
- dataIndex: 'changeAmount',
- key: 'changeAmount',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '回款年份',
- dataIndex: 'receivableYear',
- key: 'receivableYear',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '回款日期',
- dataIndex: 'receivableDate',
- key: 'receivableDate',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '回款金额',
- dataIndex: 'receivableAmount',
- key: 'receivableAmount',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '开票-回款',
- dataIndex: 'invoicedBack',
- key: 'invoicedBack',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '剩余合同金额',
- dataIndex: 'remainConAmount',
- key: 'remainConAmount',
- width: 120,
- render: (text, record) => {text || '-'}
- }, {
- title: '收入确认时间',
- dataIndex: 'incomeConfirmdate',
- key: 'incomeConfirmdate',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '第三方付款单位',
- dataIndex: 'thirdPayment',
- key: 'thirdPayment',
- width: 140,
- render: (text, record) => {text || '-'}
- }, {
- title: '备注',
- dataIndex: 'remark',
- key: 'remark',
- width: 120,
- render: (text, record) => {text || '-'}
- }]
+ const renderColumns = (columnKeys) => {
+ let columns = [];
+ Object.keys(columnKeys).map(key => {
+ switch (key) {
+ default:
+ columns.push({
+ title: columnKeys[key], dataIndex: key, key: key, width: 130,
+ render: (text, record) => text === 0 ? text : text ? text : '-'
+ });
+ break;
+ }
+ })
+ return columns;
+ }
+
function handleRow(record, index) {// 给偶数行设置斑马纹
if (index % 2 === 0) {
return {
@@ -237,7 +145,7 @@ const BackMoneyDetails = (props) => {
placeholder={SkeletonScreen()}
>
{
+ {
+ importModalV ? {
+ setImportModalV(false);
+ getBackMoneyDetails();
+ }} /> : ''
+ }
{
exportUrl ? : ''
}
diff --git a/web/client/src/sections/business/containers/performanceReport/contractDetails.jsx b/web/client/src/sections/business/containers/performanceReport/contractDetails.jsx
index b7a379b..3e9bc77 100644
--- a/web/client/src/sections/business/containers/performanceReport/contractDetails.jsx
+++ b/web/client/src/sections/business/containers/performanceReport/contractDetails.jsx
@@ -9,7 +9,7 @@ import '../../style.less';
import moment from 'moment'
const ContractDetails = (props) => {
- const { dispatch, actions } = props
+ const { dispatch, actions, user } = props
const { businessManagement } = actions;
const [keywordTarget, setKeywordTarget] = useState('contractNo');
const [keyword, setKeyword] = useState('');//搜索内容
@@ -23,6 +23,7 @@ const ContractDetails = (props) => {
const [exportUrl, setExportUrl] = useState('');
const page = useRef(query.page);
+ const scroll = useMemo(() => ({}), []);
const CONTRACTDETAILS = "contractDetails";
const renderColumns = (columnKeys) => {
@@ -33,7 +34,8 @@ const ContractDetails = (props) => {
default:
columns.push({
title: columnKeys[key], dataIndex: key, key: key,
- render: (text, record) => text === 0 ? text : text ? text : '—', width: 32 + columnKeys[key].length * 16
+ render: (text, record) => text === 0 ? text : text ? text : '—',
+ width: 32 + columnKeys[key].length * 16
});
break;
}
@@ -108,7 +110,6 @@ const ContractDetails = (props) => {
}
})
}
- const scroll = useMemo(() => ({}), []);
return (
<>
@@ -160,9 +161,10 @@ const ContractDetails = (props) => {
onClick={() => { setImportModalV(true); }}>
导入
- {
- exportAllData()
- }}>
+
{
+ exportAllData()
+ }}>
导出全部
diff --git a/web/client/src/sections/business/containers/performanceReport/importAchieveModal.jsx b/web/client/src/sections/business/containers/performanceReport/importAchieveModal.jsx
new file mode 100644
index 0000000..51036af
--- /dev/null
+++ b/web/client/src/sections/business/containers/performanceReport/importAchieveModal.jsx
@@ -0,0 +1,220 @@
+'use strict';
+import React, { useState, useEffect } from 'react';
+import { connect } from 'react-redux';
+import moment from 'moment';
+import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui';
+import { IconUpload } from '@douyinfe/semi-icons';
+import XLSX from 'xlsx';
+import { achievementColumnKeys } from '../../constants/index';
+//下载模板和上传文件读取
+const ImportAchieveModal = props => {
+ const { dispatch, actions, onCancel } = props;
+ const { businessManagement } = actions
+ const [msg, setMsg] = useState('');
+ const [loading, setLoading] = useState('');
+ const [postData, setPostData] = useState([]);
+ //初始化
+ useEffect(() => {
+ }, []);
+
+ const confirm = () => {
+ if (postData.length) {
+ setLoading(true)
+ dispatch(businessManagement.importAchieveDetails(postData)).then(res => {
+ if (res.success) {
+ onCancel()
+ }
+ setLoading(false)
+ })
+ } else {
+ Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 })
+ }
+ }
+
+ const dldCsvMb = () => {
+ //表头
+ let head = [];
+ Object.keys(achievementColumnKeys).map(key => {
+ head.push(achievementColumnKeys[key]);
+ })
+ head = head.join(',') + "\n";
+ //数据
+ //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
+ let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head;
+ //创建一个a标签
+ let link = document.createElement("a");
+ //为a标签设置属性
+ link.setAttribute("href", templateCsv);
+ link.setAttribute("download", "业绩明细表导入模板.csv");
+ //点击a标签
+ link.click();
+ }
+ const download = () => {
+ dldCsvMb();
+ }
+
+ const fileLimit = '.csv';
+
+ const getFileBlob = (url) => {
+ return new Promise((resolve, reject) => {
+ let request = new XMLHttpRequest()
+ request.open("GET", url, true)
+ request.responseType = "blob"
+ request.onreadystatechange = e => {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ if (window.FileReader) {
+ let reader = new FileReader();
+ reader.readAsBinaryString(request.response);
+ reader.onload = event => {
+ try {
+ const { result } = event.target;
+ // 以二进制流方式读取得到整份excel表格对象
+ const workbook = XLSX.read(result, {
+ type: "binary",
+ cellDates: true,//设为true,将天数的时间戳转为时间格式
+ codepage: 936//解决了乱码问题
+ });
+ let data = []; // 存储获取到的数据
+ // 遍历每张工作表进行读取(这里默认只读取第一张表)
+ for (const sheet in workbook.Sheets) {
+ if (workbook.Sheets.hasOwnProperty(sheet)) {
+ data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
+ }
+ }
+ resolve(data);//导出数据
+ } catch (e) {
+ reject("失败");
+ }
+ }
+ }
+ }
+ }
+ }
+ request.send();
+ })
+ }
+
+
+ const judgeNull = (value) => {
+ return value ? String(value).trim().replace(/\s*/g, "") : null;
+ }
+
+ const judgeNullTime = (v) => {
+ //注意的点:xlsx将excel中的时间内容解析后,会小一天
+ //如2020/8/1,xlsx会解析成 Fri Jul 31 2020 23:59:17 GMT+0800 小了43秒
+ let a = new Date(v);
+ a.setTime(a.getTime() + 43 * 1000);
+ return v ? a : null;
+ }
+
+ const judgeTimeValid = (time) => {
+ let valid = true;
+ if (!time) {
+ return valid;//可以不填
+ }
+ const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日
+ if (time instanceof Date) {
+ let timeStr = moment(time).format('YYYY/MM/DD');
+ if (!ymd.test(timeStr)) {
+ valid = false;
+ }
+ } else {
+ valid = false;
+ }
+ return valid;
+ }
+
+ return (
+ {
+ setMsg('')
+ setLoading(false)
+ setPostData([])
+ onCancel()
+ }}
+ >
+
+ {
+ setMsg('');
+ setPostData([]);
+ }}
+ customRequest={(data) => {
+ const { file, onSuccess, onError } = data
+ getFileBlob(file.url).then(res => {
+ const error = (msg) => {
+ setMsg(msg)
+ onError({ message: msg })
+ }
+ if (res.length > 1000) {
+ error('一次性上传数据行数应小于1000行,请分批上传')
+ return
+ }
+ if (!res.length) {
+ error('请填写至少一行数据')
+ return
+ }
+ let postData = [];
+ for (let i = 0; i < res.length; i++) {
+ let d = res[i];
+ let obj = {};
+ Object.keys(achievementColumnKeys).map(key => {
+ if (key === 'recConDate') {//收到合同日期
+ obj[key] = judgeNullTime(d[achievementColumnKeys[key]]);
+ } else {
+ obj[key] = d[achievementColumnKeys[key]] || null;
+ }
+ })
+ let tValid = judgeTimeValid(obj.recConDate);
+ if (!tValid) {
+ error(`第${i + 2}行【收到合同日期】错误,请填写yyyy/mm/dd格式`)
+ return
+ }
+
+ if (obj.isApproval && ['是', '否'].indexOf(obj.isApproval) == -1) {
+ error(`第${i + 2}行【价格是否特批】错误,请填写是或否`)
+ return
+ }
+
+ if (obj.reproducible && ['是', '否'].indexOf(obj.reproducible) == -1) {
+ error(`第${i + 2}行【是否可复制的业务路径】错误,请填写是或否`)
+ return
+ }
+ postData.push(obj);
+ }
+ setPostData(postData)
+ let msg = '文件解析完成,点击确定按钮上传保存!'
+ setMsg(msg)
+ onSuccess({ message: msg })
+ })
+ }}>
+ } theme="light">
+ 请选择文件
+
+
+ {msg}
+ 最大不超过200M,导入文件需与
+ download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板
+ 一致
+
+
+ )
+}
+
+function mapStateToProps(state) {
+ const { auth, global } = state;
+ return {
+ user: auth.user,
+ actions: global.actions,
+ }
+}
+
+export default connect(mapStateToProps)(ImportAchieveModal);
\ No newline at end of file
diff --git a/web/client/src/sections/business/containers/performanceReport/importBackModal.jsx b/web/client/src/sections/business/containers/performanceReport/importBackModal.jsx
new file mode 100644
index 0000000..0299946
--- /dev/null
+++ b/web/client/src/sections/business/containers/performanceReport/importBackModal.jsx
@@ -0,0 +1,256 @@
+'use strict';
+import React, { useState, useEffect } from 'react';
+import { connect } from 'react-redux';
+import moment from 'moment';
+import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui';
+import { IconUpload } from '@douyinfe/semi-icons';
+import XLSX from 'xlsx';
+import { backMoneyColumnKeys } from '../../constants/index';
+//下载模板和上传文件读取
+const ImportBackModal = props => {
+ const { dispatch, actions, onCancel } = props;
+ const { businessManagement } = actions
+ const [msg, setMsg] = useState('');
+ const [loading, setLoading] = useState('');
+ const [postData, setPostData] = useState([]);
+ const [uploadAble, setUploadAble] = useState(false);
+ const [allNumbers, setAllNumbers] = useState([]);
+ //初始化
+ useEffect(() => {
+ dispatch(businessManagement.getReceivedNumbers()).then(r => {
+ if (r.success) {
+ setUploadAble(true);
+ setAllNumbers(r.payload.data);
+ }
+ })
+ }, []);
+
+ const confirm = () => {
+ if (postData.length) {
+ setLoading(true)
+ dispatch(businessManagement.importBackDetails(postData)).then(res => {
+ if (res.success) {
+ onCancel()
+ }
+ setLoading(false)
+ })
+ } else {
+ Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 })
+ }
+ }
+
+ const dldCsvMb = () => {
+ //表头
+ let head = [];
+ Object.keys(backMoneyColumnKeys).map(key => {
+ head.push(backMoneyColumnKeys[key]);
+ })
+ head = head.join(',') + "\n";
+ //数据
+ //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
+ let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head;
+ //创建一个a标签
+ let link = document.createElement("a");
+ //为a标签设置属性
+ link.setAttribute("href", templateCsv);
+ link.setAttribute("download", "回款明细表导入模板.csv");
+ //点击a标签
+ link.click();
+ }
+ const download = () => {
+ dldCsvMb();
+ }
+
+ const fileLimit = '.csv';
+
+ const getFileBlob = (url) => {
+ return new Promise((resolve, reject) => {
+ let request = new XMLHttpRequest()
+ request.open("GET", url, true)
+ request.responseType = "blob"
+ request.onreadystatechange = e => {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ if (window.FileReader) {
+ let reader = new FileReader();
+ reader.readAsBinaryString(request.response);
+ reader.onload = event => {
+ try {
+ const { result } = event.target;
+ // 以二进制流方式读取得到整份excel表格对象
+ const workbook = XLSX.read(result, {
+ type: "binary",
+ cellDates: true,//设为true,将天数的时间戳转为时间格式
+ codepage: 936//解决了乱码问题
+ });
+ let data = []; // 存储获取到的数据
+ // 遍历每张工作表进行读取(这里默认只读取第一张表)
+ for (const sheet in workbook.Sheets) {
+ if (workbook.Sheets.hasOwnProperty(sheet)) {
+ data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
+ }
+ }
+ resolve(data);//导出数据
+ } catch (e) {
+ reject("失败");
+ }
+ }
+ }
+ }
+ }
+ }
+ request.send();
+ })
+ }
+
+
+ const judgeNull = (value) => {
+ return value ? String(value).trim().replace(/\s*/g, "") : null;
+ }
+
+ const judgeNullTime = (v) => {
+ //注意的点:xlsx将excel中的时间内容解析后,会小一天
+ //如2020/8/1,xlsx会解析成 Fri Jul 31 2020 23:59:17 GMT+0800 小了43秒
+ let a = new Date(v);
+ a.setTime(a.getTime() + 43 * 1000);
+ return v ? a : null;
+ }
+
+ const judgeTimeValid = (time) => {
+ let valid = true;
+ if (!time) {
+ return valid;//可以不填
+ }
+ const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日
+ if (time instanceof Date) {
+ let timeStr = moment(time).format('YYYY/MM/DD');
+ if (!ymd.test(timeStr)) {
+ valid = false;
+ }
+ } else {
+ valid = false;
+ }
+ return valid;
+ }
+
+ return (
+ {
+ setMsg('')
+ setLoading(false)
+ setPostData([])
+ onCancel()
+ }}
+ >
+
+ {
+ setMsg('');
+ setPostData([]);
+ }}
+ customRequest={(data) => {
+ const { file, onSuccess, onError } = data
+ getFileBlob(file.url).then(res => {
+ const error = (msg) => {
+ setMsg(msg)
+ onError({ message: msg })
+ }
+ if (res.length > 1000) {
+ error('一次性上传数据行数应小于1000行,请分批上传')
+ return
+ }
+ if (!res.length) {
+ error('请填写至少一行数据')
+ return
+ }
+ let postData = [];
+ const numPattern = /^\d+(\.\d+)?$/;//非负浮点数 判断金额
+ for (let i = 0; i < res.length; i++) {
+ let d = res[i];
+ let obj = {};
+ Object.keys(backMoneyColumnKeys).map(key => {
+ if (['receivableDate', 'incomeConfirmdate'].indexOf(key) != -1) {//两个时间
+ obj[key] = judgeNullTime(d[backMoneyColumnKeys[key]]);
+ } else {
+ obj[key] = d[backMoneyColumnKeys[key]] || null;
+ }
+ })
+ //年度 序号 编号必填
+ if (!obj.year || !obj.serialNo || !obj.number) {
+ error(`第${i + 2}行【年度】、【序号】、【编号】存在空值,请填写`)
+ return
+ }
+ let exist = allNumbers.find(m => m.number == obj.number);//数据库中 已有该编号
+ if (exist) {
+ error(`第${i + 2}行的【编号】在系统中已存在`)
+ return
+ }
+ if (postData.some(p => p.number == obj.number)) {//编号 唯一
+ error(`第${i + 2}行【编号】重复,请更改后重新上传`)
+ return
+ }
+ //金额 数字
+ if (obj.amount && !numPattern.test(obj.amount)) {
+ error(`第${i + 2}行【合同金额】填写错误,需要为数字`)
+ return
+ }
+ if (obj.changeAmount && !numPattern.test(obj.changeAmount)) {
+ error(`第${i + 2}行【变更后合同金额】填写错误,需要为数字`)
+ return
+ }
+ if (obj.receivableAmount && !numPattern.test(obj.receivableAmount)) {
+ error(`第${i + 2}行【回款金额】填写错误,需要为数字`)
+ return
+ }
+ if (obj.remainConAmount && !numPattern.test(obj.remainConAmount)) {
+ error(`第${i + 2}行【剩余合同金额】填写错误,需要为数字`)
+ return
+ }
+ //时间
+ let tValid = judgeTimeValid(obj.receivableDate);
+ if (!tValid) {
+ error(`第${i + 2}行回款日期错误,请填写yyyy/mm/dd格式`)
+ return
+ }
+ let cValid = judgeTimeValid(obj.incomeConfirmdate);
+ if (!cValid) {
+ error(`第${i + 2}行收入确认时间错误,请填写yyyy/mm/dd格式`)
+ return
+ }
+ postData.push(obj);
+ }
+ setPostData(postData)
+ let msg = '文件解析完成,点击确定按钮上传保存!'
+ setMsg(msg)
+ onSuccess({ message: msg })
+ })
+ }}>
+ } theme="light" disabled={!uploadAble}>
+ 请选择文件
+
+
+ {msg}
+ 最大不超过200M,导入文件需与
+ download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板
+ 一致
+
+
+ )
+}
+
+function mapStateToProps(state) {
+ const { auth, global } = state;
+ return {
+ user: auth.user,
+ actions: global.actions,
+ }
+}
+
+export default connect(mapStateToProps)(ImportBackModal);
\ No newline at end of file
diff --git a/web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx b/web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx
index 91c1fe3..9a76a9b 100644
--- a/web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx
+++ b/web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef, useMemo } from 'react';
import { connect } from 'react-redux';
-import { Select, Input, Button, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
+import { Select, Input, Button, Table, Pagination, Skeleton, Toast, } from '@douyinfe/semi-ui';
import { SkeletonScreen, Setup } from "$components";
import { IconSearch } from '@douyinfe/semi-icons';
import ImportInvoicingDetailsModal from './importInvoicingDetailsModal';
@@ -9,8 +9,8 @@ import '../../style.less';
import moment from 'moment'
const InvoicingDetails = (props) => {
- const { dispatch, actions } = props
- const { } = actions;
+ const { dispatch, actions, user } = props
+ const { businessManagement } = actions;
const [keywordTarget, setKeywordTarget] = useState('contractNo');
const [keyword, setKeyword] = useState('');//搜索内容
const [limits, setLimits] = useState()//每页实际条数
@@ -20,6 +20,11 @@ const InvoicingDetails = (props) => {
const [setup, setSetup] = useState(false);//表格设置是否显现
const [setupp, setSetupp] = useState([]);//实际显示的表格列表
+
+ const [exportUrl, setExportUrl] = useState('');
+ const page = useRef(query.page);
+ const scroll = useMemo(() => ({}), []);
+
const INVOICINGDETAILS = "invoicingDetails";
const renderColumns = (columnKeys) => {
let columns = [];
@@ -29,7 +34,8 @@ const InvoicingDetails = (props) => {
default:
columns.push({
title: columnKeys[key], dataIndex: key, key: key,
- render: (text, record) => text === 0 ? text : text ? text : '—', width: 32 + columnKeys[key].length * 16
+ render: (text, record) => text === 0 ? text : text ? text : '—',
+ width: 32 + columnKeys[key].length * 16
});
break;
}
@@ -52,6 +58,7 @@ const InvoicingDetails = (props) => {
)
: "";
attribute();
+ getInvoicingDetailData();
}, []);
//获取表格属性设置
function attribute() {
@@ -66,9 +73,16 @@ const InvoicingDetails = (props) => {
}
setSetupp(newColumns);
}
- useEffect(() => {
- }, [query])
+ function getInvoicingDetailData(param) {
+ let queryParam = param || query;
+ dispatch(businessManagement.getInvoicingDetail({ keywordTarget, keyword, ...queryParam })).then(r => {
+ if (r.success) {
+ setTableData(r.payload?.data?.rows);
+ setLimits(r.payload?.data?.count);
+ }
+ })
+ }
function handleRow(record, index) {// 给偶数行设置斑马纹
if (index % 2 === 0) {
return {
@@ -80,7 +94,22 @@ const InvoicingDetails = (props) => {
return {};
}
}
- const scroll = useMemo(() => ({}), []);
+
+ const exportAllData = () => {
+ dispatch(businessManagement.getInvoicingDetail({ limit: 1, page: 0 })).then((res) => {
+ if (res.success) {
+ if (res.payload.data.count) {
+ let url = `export/invoicing/detail?token=${user.token}×tamp=${moment().valueOf()}`
+ setExportUrl(url);
+ } else {
+ Toast.info({
+ content: '暂无可导出的数据',
+ duration: 3,
+ })
+ }
+ }
+ })
+ }
return (
<>
@@ -120,6 +149,7 @@ const InvoicingDetails = (props) => {
@@ -130,7 +160,10 @@ const InvoicingDetails = (props) => {
onClick={() => { setImportModalV(true); }}>
导入
-
+
{
+ exportAllData()
+ }}>
导出全部
@@ -185,20 +218,25 @@ const InvoicingDetails = (props) => {
setImportModalV(false);
}} /> : ''
}
+ {
+ exportUrl ? : ''
+ }
- {setup ? (
- {
- setSetup(false);
- attribute();
- }}
- />
- ) : (
- ""
- )}
+ {
+ setup ? (
+ {
+ setSetup(false);
+ attribute();
+ }}
+ />
+ ) : (
+ ""
+ )
+ }
>
)
}
diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js
index b82e855..60b361d 100644
--- a/web/client/src/utils/webapi.js
+++ b/web/client/src/utils/webapi.js
@@ -5,31 +5,37 @@ export const AxyRequest = new ProxyRequest("_axy");
export const EmisRequest = new ProxyRequest("_emis")
export const webUtils = new customWebUtils({
- userKey: 'dcUser'
+ userKey: 'dcUser'
});
const { basicAction, RouteRequest } = webUtils
export {
- basicAction, RouteRequest
+ basicAction, RouteRequest
}
export const ApiTable = {
- login: "login",
- logout: "logout",
+ login: "login",
+ logout: "logout",
- //项目报表
- getReserveItemReport: "reserveItem/report/{type}",
- //销售人员分布明细表
- getSalesList: 'sales/member/list',
- //业绩报表
- getReceivedDetail: 'detail/received/back',
- getAchievementDetail: 'detail/achievement',
- getContractDetail: 'contract/detail',
- //客户联系人对接跟进
- getCustomerContactsFollowup:'customerContactsFollup'
+ //项目报表
+ getReserveItemReport: "reserveItem/report/{type}",
+ //销售人员分布明细表
+ getSalesList: 'sales/member/list',
+ //业绩报表
+ getReceivedDetail: 'detail/received/back',
+ getAchievementDetail: 'detail/achievement',
+
+ getReceivedNumbers: 'detail/received/numbers',
+ importBackDetails: 'add/received/back/bulk',
+ importAchieveDetails: 'add/achievement/bulk',
+
+ getContractDetail: 'contract/detail',
+ getInvoicingDetail: 'invoicing/detail',
+ //客户联系人对接跟进
+ getCustomerContactsFollowup:'customerContactsFollup'
};
export const RouteTable = {
- apiRoot: "/api/root",
- fileUpload: "/_upload/new",
- cleanUpUploadTrash: "/_upload/cleanup",
- getServiceUrl: '/_service/url'
+ apiRoot: "/api/root",
+ fileUpload: "/_upload/new",
+ cleanUpUploadTrash: "/_upload/cleanup",
+ getServiceUrl: '/_service/url'
};
diff --git a/web/config.js b/web/config.js
index 076ab7c..cc4aa5b 100644
--- a/web/config.js
+++ b/web/config.js
@@ -12,6 +12,10 @@ dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,
args.option(['p', 'port'], '启动端口');
args.option(['u', 'api-url'], 'webapi的URL');
args.option('apiHrUrl', 'webapi的URL 外网可访问');
+args.option(['d', 'domain'], 'web domain');
+args.option('webPepUrl', '企业管理 web');
+
+
// 七牛
args.option('qnak', 'qiniuAccessKey');
@@ -24,6 +28,8 @@ const flags = args.parse(process.argv);
const API_URL = process.env.API_URL || flags.apiUrl;
const API_DC_URL = process.env.API_DC_URL || flags.apiHrUrl;
+const FS_REPORT_DOMAIN = process.FS_REPORT_DOMAIN || flags.domain;
+const WEB_PEP_URL = process.env.WEB_PEP_URL || flags.webPepUrl;
// 七牛
const ANXINCLOUD_QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak;
@@ -76,7 +82,9 @@ const product = {
},
service: {
url: ANXINCLOUD_PM_SERVICES
- }
+ },
+ domain: FS_REPORT_DOMAIN,
+ webPepUrl: WEB_PEP_URL
}
}, {
entry: require('./client').entry,// 静态信息
diff --git a/web/package.json b/web/package.json
index a9521a2..c66ff68 100644
--- a/web/package.json
+++ b/web/package.json
@@ -7,7 +7,7 @@
"test": "mocha",
"start-vite": "cross-env NODE_ENV=developmentVite npm run start-params",
"start": "cross-env NODE_ENV=development npm run start-params",
- "start-params": "node server -p 5700 -u http://localhost:4700 --apiHrUrl http://localhost:4700 --qnak 5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu --qnsk w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5 --qnbkt pep-process-report --qndmn https://pepsource.anxinyun.cn --pmrs http://10.8.30.109:14000",
+ "start-params": "node server -p 5700 -u http://localhost:4700 --apiHrUrl http://localhost:4700 -d localhost --webPepUrl http://localhost:5300 --qnak 5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu --qnsk w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5 --qnbkt pep-process-report --qndmn https://pepsource.anxinyun.cn --pmrs http://10.8.30.109:14000",
"deploy": "export NODE_ENV=production&& npm run build && node server",
"build-dev": "cross-env NODE_ENV=development&&webpack --config webpack.config.js",
"build": "cross-env NODE_ENV=production&&webpack --config webpack.config.prod.js"
@@ -60,6 +60,7 @@
"ezuikit-js": "^0.6.1",
"fs-attachment": "^1.0.0",
"fs-web-server-scaffold": "^1.0.6",
+ "js-cookie": "^3.0.1",
"js-export-excel": "^1.1.4",
"koa-better-http-proxy": "^0.2.5",
"koa-proxy": "^1.0.0-alpha.3",
diff --git a/web/routes/attachment/index.js b/web/routes/attachment/index.js
index 5f31761..7e6f1b4 100644
--- a/web/routes/attachment/index.js
+++ b/web/routes/attachment/index.js
@@ -20,11 +20,13 @@ module.exports = {
entry: function (app, router, opts) {
const getApiRoot = async function (ctx) {
- const { apiUrl } = opts;
+ const { apiUrl, domain, webPepUrl } = opts;
ctx.status = 200;
ctx.body = {
root: apiUrl,
+ domain: domain,
+ webPepUrl
};
};