Commit c1f5be7741d0723a3ecd908473a8ca720ba3dcfe

Authored by boyang
2 parents cfb654e3 655ab711

fix: bug修复

Showing 41 changed files with 2862 additions and 492 deletions
src/api/project/approve.ts
... ... @@ -21,12 +21,11 @@ export const approveAuditApi = async (params: any) =>
21 21 { message: '操作成功' },
22 22 );
23 23  
24   -export const getWaitListApi = async (params: DemoParams) => {
  24 +export const getWaitListApi = async (params: any) => {
25 25 const res = await defHttp.post({
26 26 url: Api.APPROVE,
27 27 params,
28 28 });
29   -
30 29 res.records = res.records.map((item) => {
31 30 return item;
32 31 });
... ... @@ -44,7 +43,6 @@ export const getApprovedListApi = async (params: any) => {
44 43 res.records = res.records.map((item) => {
45 44 return item;
46 45 });
47   -
48 46 return new Promise((resolve) => {
49 47 resolve({ items: res.records, total: res.total });
50 48 });
... ...
src/api/project/invoice.ts
... ... @@ -52,6 +52,12 @@ enum Api {
52 52 SERVICEAPPLYEDIT = '/project/applyEditFileds', //申请修改
53 53 APPLYLIST = '/project/pageProjectLockFieldApply', //分页查询项目字段申请记录
54 54 AUDITAPPLY = '/project/audit', //审核
  55 + ACTIONRECORD = '/projectOptLog/listByPage', //业务/内部研发净利润操作记录以及申请记录
  56 + ORDERCOSTDETAILEDOPTLOG = '/orderCostDetailedOptLog/listByPage', //包装费用明细/内部生产明细操作记录表
  57 + SETPACKSTATUS = '/order/cost/setPackStatus', //包装费用明细表审批
  58 + SETINNERSTATUS = '/order/cost/setInnerStatus', //内部生产明细表审批
  59 + PROJECTBUSINESSPROFITSETSTATUS = '/project/businessProfit/setStatus', //业务研发净利润分析表审批
  60 + PROJECTINNERPROFITSETSTATUS = '/project/innerProfitInfo/setStatus', //内部研发净利润分析表审批
55 61 }
56 62  
57 63 export const getRefundDate = async (params: any, data?: any) => {
... ... @@ -429,18 +435,30 @@ export const getServiceApplyEdit = async (params: any) => {
429 435 };
430 436  
431 437 export const getApplyList = async (params: any) => {
432   - const res = ref();
433   - res.value = await defHttp.post<any>({
  438 + // const res = ref();
  439 + // res.value = await defHttp.post<any>({
  440 + // url: Api.APPLYLIST,
  441 + // params,
  442 + // });
  443 + // res.value.data.records.forEach((record) => {
  444 + // const jsonObj = JSON.parse(record?.fields);
  445 + // if (jsonObj) {
  446 + // record.fields = jsonObj;
  447 + // }
  448 + // });
  449 + // return res.value.data.records;
  450 + const data = await defHttp.post({
434 451 url: Api.APPLYLIST,
435 452 params,
436 453 });
437   - res.value.data.records.forEach((record) => {
438   - const jsonObj = JSON.parse(record?.fields);
439   - if (jsonObj) {
440   - record.fields = jsonObj;
441   - }
  454 + const res = data.data;
  455 + res.records = res.records.map((item) => {
  456 + return item;
  457 + });
  458 +
  459 + return new Promise((resolve) => {
  460 + resolve({ items: res.records, total: res.total });
442 461 });
443   - return res.value.data.records;
444 462 };
445 463  
446 464 export const getAuditApply = async (params: any) => {
... ... @@ -476,4 +494,71 @@ export const checkSetFinishStatus = async (params: any) =&gt; {
476 494 url: Api.SETCHECKFINISHSTATUS,
477 495 params,
478 496 });
479   -};
480 497 \ No newline at end of file
  498 +};
  499 +export const getProjectOptLog = async (params: any) => {
  500 + const res = await defHttp.post<any>({
  501 + url: Api.ACTIONRECORD,
  502 + params,
  503 + });
  504 + const formattedRecords = res.records.map((record: any) => {
  505 + return {
  506 + ...record,
  507 + };
  508 + });
  509 + const orderStore = useOrderStoreWithOut();
  510 + orderStore.setTotal(res.total);
  511 + orderStore.setQueryVO(params);
  512 + return new Promise((resolve) => {
  513 + resolve({
  514 + items: formattedRecords,
  515 + total: res.total,
  516 + });
  517 + });
  518 +};
  519 +
  520 +export const getOrderCostDetailedOptLog = async (params: any) => {
  521 + const res = await defHttp.post<any>({
  522 + url: Api.ORDERCOSTDETAILEDOPTLOG,
  523 + params,
  524 + });
  525 + const formattedRecords = res.records.map((record: any) => {
  526 + return {
  527 + ...record,
  528 + };
  529 + });
  530 + const orderStore = useOrderStoreWithOut();
  531 + orderStore.setTotal(res.total);
  532 + orderStore.setQueryVO(params);
  533 + return new Promise((resolve) => {
  534 + resolve({
  535 + items: formattedRecords,
  536 + total: res.total,
  537 + });
  538 + });
  539 +};
  540 +export const setPackStatus = async (params: any) => {
  541 + return await defHttp.post<any>({
  542 + url: Api.SETPACKSTATUS,
  543 + params,
  544 + });
  545 +};
  546 +export const setInnerStatus = async (params: any) => {
  547 + return await defHttp.post<any>({
  548 + url: Api.SETINNERSTATUS,
  549 + params,
  550 + });
  551 +};
  552 +
  553 +export const setBusinessProfitSetStatus = async (params: any) => {
  554 + return await defHttp.post<any>({
  555 + url: Api.PROJECTBUSINESSPROFITSETSTATUS,
  556 + params,
  557 + });
  558 +};
  559 +
  560 +export const setInnerProfitSetStatus = async (params: any) => {
  561 + return await defHttp.post<any>({
  562 + url: Api.PROJECTINNERPROFITSETSTATUS,
  563 + params,
  564 + });
  565 +};
... ...
src/router/routes/modules/project/finance.ts
... ... @@ -43,106 +43,106 @@ const finance: AppRouteModule = {
43 43 },
44 44 component: () => import('/@/views/project/finance/financeList/index.vue'),
45 45 },
46   - // {
47   - // path: 'financeProfit',
48   - // name: 'FinanceProfit',
49   - // meta: {
50   - // title: '净利润分析',
51   - // ignoreKeepAlive: true,
52   - // roles: [
53   - // RoleEnum.ADMIN,
54   - // RoleEnum.FINANCE,
55   - // // RoleEnum.TRACKER,
56   - // // RoleEnum.BUSINESS,
57   - // // RoleEnum.PRODUCE,
58   - // // RoleEnum.DATA_REPORT_USER,
59   - // ],
60   - // },
61   - // children: [
62   - // // {
63   - // // path: '',
64   - // // name: 'Receive',
65   - // // meta: {
66   - // // title: '财务管理',
67   - // // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
68   - // // ignoreKeepAlive: false,
69   - // // },
70   - // // // component: () => import('/@/views/project/finance/index.vue'),
71   - // // },
72   - // {
73   - // path: 'serviceProfit',
74   - // name: 'ServiceProfit',
75   - // meta: {
76   - // title: '业务研发净利润分析',
77   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
78   - // ignoreKeepAlive: false,
79   - // },
80   - // children: [
81   - // {
82   - // path: 'ServiceProfit',
83   - // name: 'ServiceProfit',
84   - // meta: {
85   - // title: '业务研发净利润分析表',
86   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
87   - // ignoreKeepAlive: false,
88   - // },
89   - // component: () =>
90   - // import(
91   - // '/@/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/index.vue'
92   - // ),
93   - // },
94   - // {
95   - // path: 'PackageProfit',
96   - // name: 'PackageProfit',
97   - // meta: {
98   - // title: '包装费用明细表',
99   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
100   - // ignoreKeepAlive: false,
101   - // },
102   - // component: () =>
103   - // import(
104   - // '/@/views/project/finance/financeProfit/ServiceProfit/PackageProfit/index.vue'
105   - // ),
106   - // },
107   - // ],
108   - // },
109   - // {
110   - // path: 'ProductProfit',
111   - // name: 'ProductProfit',
112   - // meta: {
113   - // title: '内部生产净利润分析',
114   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
115   - // ignoreKeepAlive: false,
116   - // },
117   - // children: [
118   - // {
119   - // path: 'InnerData',
120   - // name: 'InnerData',
121   - // meta: {
122   - // title: '内部生产净利润分析表',
123   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
124   - // ignoreKeepAlive: false,
125   - // },
126   - // component: () =>
127   - // import(
128   - // '/@/views/project/finance/financeProfit/ProductProfit/InnerProduce/index.vue'
129   - // ),
130   - // },
131   - // {
132   - // path: 'InnerProduce',
133   - // name: 'InnerProduce',
134   - // meta: {
135   - // title: '内部生产明细表',
136   - // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
137   - // ignoreKeepAlive: false,
138   - // },
139   - // component: () =>
140   - // import('/@/views/project/finance/financeProfit/ProductProfit/InnerData/index.vue'),
141   - // },
142   - // ],
143   - // },
144   - // ],
145   - // },
  46 + {
  47 + path: 'financeProfit',
  48 + name: 'FinanceProfit',
  49 + meta: {
  50 + title: '净利润分析',
  51 + ignoreKeepAlive: true,
  52 + roles: [
  53 + RoleEnum.ADMIN,
  54 + RoleEnum.FINANCE,
  55 + // RoleEnum.TRACKER,
  56 + // RoleEnum.BUSINESS,
  57 + // RoleEnum.PRODUCE,
  58 + // RoleEnum.DATA_REPORT_USER,
  59 + ],
  60 + },
  61 + children: [
  62 + // {
  63 + // path: '',
  64 + // name: 'Receive',
  65 + // meta: {
  66 + // title: '财务管理',
  67 + // roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  68 + // ignoreKeepAlive: false,
  69 + // },
  70 + // // component: () => import('/@/views/project/finance/index.vue'),
  71 + // },
  72 + {
  73 + path: 'serviceProfit',
  74 + name: 'ServiceProfit',
  75 + meta: {
  76 + title: '业务研发净利润分析',
  77 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  78 + ignoreKeepAlive: false,
  79 + },
  80 + children: [
  81 + {
  82 + path: 'ServiceProfit',
  83 + name: 'ServiceProfit',
  84 + meta: {
  85 + title: '业务研发净利润分析表',
  86 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  87 + ignoreKeepAlive: false,
  88 + },
  89 + component: () =>
  90 + import(
  91 + '/@/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/index.vue'
  92 + ),
  93 + },
  94 + {
  95 + path: 'PackageProfit',
  96 + name: 'PackageProfit',
  97 + meta: {
  98 + title: '包装费用明细表',
  99 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  100 + ignoreKeepAlive: false,
  101 + },
  102 + component: () =>
  103 + import(
  104 + '/@/views/project/finance/financeProfit/ServiceProfit/PackageProfit/index.vue'
  105 + ),
  106 + },
  107 + ],
  108 + },
  109 + {
  110 + path: 'ProductProfit',
  111 + name: 'ProductProfit',
  112 + meta: {
  113 + title: '内部生产净利润分析',
  114 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  115 + ignoreKeepAlive: false,
  116 + },
  117 + children: [
  118 + {
  119 + path: 'InnerProduce',
  120 + name: 'InnerProduce',
  121 + meta: {
  122 + title: '内部生产净利润分析表',
  123 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  124 + ignoreKeepAlive: false,
  125 + },
  126 + component: () =>
  127 + import(
  128 + '/@/views/project/finance/financeProfit/ProductProfit/InnerProduce/index.vue'
  129 + ),
  130 + },
  131 + {
  132 + path: 'InnerData',
  133 + name: 'InnerData',
  134 + meta: {
  135 + title: '内部生产明细表',
  136 + roles: [RoleEnum.ADMIN, RoleEnum.FINANCE, RoleEnum.TRACKER, RoleEnum.BUSINESS],
  137 + ignoreKeepAlive: false,
  138 + },
  139 + component: () =>
  140 + import('/@/views/project/finance/financeProfit/ProductProfit/InnerData/index.vue'),
  141 + },
  142 + ],
  143 + },
  144 + ],
  145 + },
146 146 ],
147 147 };
148 148  
... ...
src/views/project/approve/ProduceFieldPanel.vue
... ... @@ -94,7 +94,7 @@
94 94 dataIndex: 'innerNo',
95 95 width: 150,
96 96 customRender: (column) => {
97   - return column.record.orderBaseInfo.innerNo;
  97 + return column.record.orderBaseInfo?.innerNo || '-';
98 98 },
99 99 },
100 100 {
... ... @@ -102,7 +102,7 @@
102 102 dataIndex: 'projectNo',
103 103 width: 150,
104 104 customRender: (column) => {
105   - return column.record.orderBaseInfo.projectNo;
  105 + return column.record.orderBaseInfo?.projectNo || '-';
106 106 },
107 107 },
108 108 {
... ... @@ -130,7 +130,7 @@
130 130 const [registerTable, { reload }] = useTable({
131 131 scroll: false,
132 132 api: props.isApproved ? getApprovedListApi : getWaitListApi,
133   - searchInfo: { typeIn: [60, 70] },
  133 + searchInfo: { typeIn: [80, 90] },
134 134 columns,
135 135 useSearchForm: false,
136 136 formConfig: getFormConfig('true'),
... ...
src/views/project/approve/ProfitFieldPanel.vue
... ... @@ -39,7 +39,7 @@
39 39 import { BasicDrawer, useDrawer } from '/@/components/Drawer';
40 40 import { approveAuditApi, getApprovedListApi, getWaitListApi } from '/@/api/project/approve';
41 41 import { getAuditApply, getApplyList } from '/@/api/project/invoice';
42   - import { FIELDS_BASE_INFO } from '../finance/financeProfit/ServiceProfit/ServiceProfit/tableData';
  42 + import { FIELDS_BASE_INFO } from './data';
43 43 import { COLUMNS } from '../finance/financeProfit/ServiceProfit/ServiceProfit/data';
44 44 import { find, isEmpty } from 'lodash-es';
45 45 import { ROLE } from '../order//type.d';
... ... @@ -85,7 +85,11 @@
85 85 dataIndex: 'auditRoleCodes',
86 86 width: 150,
87 87 customRender: (column) => {
88   - return '业务研发净利字段';
  88 + if (column.record.type === 'FIELD_EDIT_APPLY') {
  89 + return '业务利润字段编辑申请';
  90 + } else if (column.record.type === 'INNER_PROFIT_FIELD_EDIT_APPLY') {
  91 + return '内部利润字段编辑申请';
  92 + }
89 93 },
90 94 },
91 95 {
... ...
src/views/project/approve/data.ts
... ... @@ -70,4 +70,63 @@ export function getFormConfig(showFieldConfig: string) {
70 70 : []),
71 71 ],
72 72 };
73   -}
74 73 \ No newline at end of file
  74 +}
  75 +
  76 +export const FIELDS_BASE_INFO = [
  77 + {
  78 + field: 'developmentCopyRmbTotalPrice',
  79 + component: 'Select',
  80 + labelWidth: 150,
  81 + label: '研发复制费合计¥',
  82 + rules: [{ required: true }],
  83 + },
  84 + {
  85 + field: 'projectStartTime',
  86 + component: 'Select',
  87 + labelWidth: 150,
  88 + label: '项目开始时间',
  89 + rules: [{ required: true }],
  90 + },
  91 + {
  92 + field: 'projectEndTime',
  93 + component: 'Select',
  94 + labelWidth: 150,
  95 + label: '项目结束时间',
  96 + rules: [{ required: true }],
  97 + },
  98 + {
  99 + field: 'spainPaidRmbCommission',
  100 + component: 'Select',
  101 + labelWidth: 150,
  102 + label: '西班牙已发提成¥',
  103 + rules: [{ required: true }],
  104 + },
  105 + {
  106 + field: 'paidRmbCommission',
  107 + component: 'Select',
  108 + labelWidth: 150,
  109 + label: '中国团队已发提成¥',
  110 + rules: [{ required: true }],
  111 + },
  112 + {
  113 + field: 'actualExchangeRate',
  114 + component: 'Select',
  115 + labelWidth: 150,
  116 + label: '实际汇率¥',
  117 + rules: [{ required: true }],
  118 + },
  119 + {
  120 + field: 'projectInnerProfitInfoStartTime',
  121 + component: 'Select',
  122 + labelWidth: 150,
  123 + label: '项目开始时间',
  124 + rules: [{ required: true }],
  125 + },
  126 + {
  127 + field: 'projectInnerProfitInfoEndTime',
  128 + component: 'Select',
  129 + labelWidth: 150,
  130 + label: '项目结束时间',
  131 + rules: [{ required: true }],
  132 + },
  133 +];
... ...
src/views/project/approve/index.vue
... ... @@ -35,7 +35,7 @@
35 35 >
36 36 <PayPanel />
37 37 </a-tab-pane>
38   - <!-- <a-tab-pane
  38 + <a-tab-pane
39 39 key="11"
40 40 tab="利润分析表字段审核"
41 41 v-if="role == ROLE.FINANCE || role == ROLE.ADMIN"
... ... @@ -44,7 +44,7 @@
44 44 </a-tab-pane>
45 45 <a-tab-pane key="13" tab="明细表字段审核" v-if="role == ROLE.FINANCE || role == ROLE.ADMIN">
46 46 <ProduceFieldPanel />
47   - </a-tab-pane> -->
  47 + </a-tab-pane>
48 48 <a-tab-pane key="2" tab="字段已审核" v-if="role !== ROLE.FINANCE">
49 49 <FieldPanel isApproved />
50 50 </a-tab-pane>
... ... @@ -79,7 +79,7 @@
79 79 >
80 80 <PayPanel isApproved />
81 81 </a-tab-pane>
82   - <!-- <a-tab-pane
  82 + <a-tab-pane
83 83 key="12"
84 84 tab="利润分析表字段已审核"
85 85 v-if="role == ROLE.FINANCE || role == ROLE.ADMIN"
... ... @@ -88,7 +88,7 @@
88 88 </a-tab-pane>
89 89 <a-tab-pane key="14" tab="明细表字段审核" v-if="role == ROLE.FINANCE || role == ROLE.ADMIN">
90 90 <ProduceFieldPanel isApproved />
91   - </a-tab-pane> -->
  91 + </a-tab-pane>
92 92 </a-tabs>
93 93 </div>
94 94 </template>
... ...
src/views/project/config/ProduCostCreate.vue
... ... @@ -22,9 +22,15 @@
22 22 <a-input v-model:value="fixCost"
23 23 /></div>
24 24 <div
25   - ><span style="margin-right: 8px; width: 80%">提成比例:</span>
  25 + ><span style="margin-right: 8px; width: 80%">提成单价:</span>
26 26 <a-input v-model:value="ratio" />
27 27 </div>
  28 + <div
  29 + ><span style="margin-right: 8px; width: 80%">年份:</span>
  30 + <a-select v-model:value="year" style="width: 100%">
  31 + <a-select-option v-for="y in yearOptions" :key="y" :value="y">{{ y }}</a-select-option>
  32 + </a-select>
  33 + </div>
28 34 </a-space>
29 35 </BasicModal>
30 36 </template>
... ... @@ -54,6 +60,13 @@
54 60 //获取现有的列表
55 61 const listAll = ref();
56 62 const fixCost = ref();
  63 + const year = ref(new Date().getFullYear().toString());
  64 + // Generate year options from 2023 to current year
  65 + const currentYear = new Date().getFullYear();
  66 + const yearOptions = ref<string[]>([]);
  67 + for (let y = 2023; y <= currentYear; y++) {
  68 + yearOptions.value.push(y.toString());
  69 + }
57 70 const ratio = ref();
58 71 // const relationValue = ref();
59 72 const customerCode = ref();
... ... @@ -91,7 +104,7 @@
91 104 settingValue: customerCode.value,
92 105 settingType: 4,
93 106 relationCode: 'ProduceSettingItem',
94   - relationName: '成本配置项集合',
  107 + relationName: year.value,
95 108 costSettingItemVOS: relationValue.value,
96 109 });
97 110 emit('modal-success');
... ... @@ -108,6 +121,8 @@
108 121 activeUser,
109 122 fixCost,
110 123 ratio,
  124 + year,
  125 + yearOptions,
111 126 customerCode,
112 127 customerCodeOptions,
113 128 handleOk,
... ...
src/views/project/config/ProduCostEdit.vue
... ... @@ -16,7 +16,8 @@
16 16 >
17 17 <a-space direction="vertical" style="width: 100%">
18 18 <a-input v-model:value="fixCost" addonBefore="固定成本 " />
19   - <a-input v-model:value="ratio" addonBefore="提成比例 " />
  19 + <a-input v-model:value="ratio" addonBefore="提成单价 " />
  20 + <a-input v-model:value="year" :disabled="true" addonBefore="年份 " />
20 21 </a-space>
21 22 </BasicDrawer>
22 23 </template>
... ... @@ -35,13 +36,14 @@
35 36 relationValue.value = JSON.parse(listAll.value.relationValue);
36 37 fixCost.value = relationValue.value[0].relationValue;
37 38 ratio.value = relationValue.value[1].relationValue;
  39 + year.value = listAll.value.relationName;
38 40 });
39 41 //获取现有的列表
40 42 const listAll = ref();
41 43 const fixCost = ref();
42 44 const ratio = ref();
43 45 const relationValue = ref();
44   -
  46 + const year = ref();
45 47 //完成编辑
46 48 async function handleSubmit() {
47 49 relationValue.value[0].relationValue = fixCost.value;
... ... @@ -53,7 +55,7 @@
53 55 settingValue: listAll.value.settingValue,
54 56 settingType: 4,
55 57 relationCode: 'ProduceSettingItem',
56   - relationName: '成本配置项集合',
  58 + relationName: year.value,
57 59 costSettingItemVOS: relationValue.value,
58 60 });
59 61 emit('success');
... ...
src/views/project/config/TablePanel.vue
... ... @@ -46,7 +46,7 @@
46 46 import { BasicTable, useTable, TableAction } from '/@/components/Table';
47 47 import CreateModal from './CreateModal.vue';
48 48 import { deleteConfig, getList, saveConfig } from '/@/api/sys/config';
49   - import { COLUMNS } from './data';
  49 + import { COLUMNS, searchFormSchema } from './data';
50 50 import { useModal } from '/@/components/Modal';
51 51 import { useDrawer } from '/@/components/Drawer';
52 52 import CostEdit from './costEdit.vue';
... ... @@ -74,6 +74,10 @@
74 74 pagination: false,
75 75 columns: COLUMNS[props.column!],
76 76 rowKey: 'id',
  77 + useSearchForm: props.column === 6 || props.column === 9,
  78 + formConfig: {
  79 + schemas: props.column === 6 || props.column === 9 ? searchFormSchema : [],
  80 + },
77 81 actionColumn: {
78 82 width: 160,
79 83 title: 'Action',
... ...
src/views/project/config/costCreate.vue
... ... @@ -29,6 +29,12 @@
29 29 ><span style="margin-right: 8px; width: 80%">西班牙提成比例:</span>
30 30 <a-input v-model:value="spainRatio" />
31 31 </div>
  32 + <div
  33 + ><span style="margin-right: 8px; width: 80%">年份:</span>
  34 + <a-select v-model:value="year" style="width: 100%">
  35 + <a-select-option v-for="y in yearOptions" :key="y" :value="y">{{ y }}</a-select-option>
  36 + </a-select>
  37 + </div>
32 38 <!-- <div
33 39 ><span style="margin-right: 8px; width: 80%">生产提成单价:</span>
34 40 <a-input v-model:value="price" />
... ... @@ -65,6 +71,13 @@
65 71 const ratio = ref();
66 72 const spainRatio = ref();
67 73 const price = ref();
  74 + const year = ref(new Date().getFullYear().toString());
  75 + // Generate year options from 2023 to current year
  76 + const currentYear = new Date().getFullYear();
  77 + const yearOptions = ref<string[]>([]);
  78 + for (let y = 2023; y <= currentYear; y++) {
  79 + yearOptions.value.push(y.toString());
  80 + }
68 81 // const relationValue = ref();
69 82 const customerCode = ref();
70 83 const relationValue = ref([
... ... @@ -89,8 +102,6 @@
89 102 // relationValue: '',
90 103 // },
91 104 ]);
92   - // const loading = ref(true);
93   -
94 105 const { customerCode: customerCodeOptions } = useOrderInfo(orderStore);
95 106  
96 107 function handleShow(visible: boolean) {
... ... @@ -101,18 +112,30 @@
101 112 }
102 113 }
103 114  
  115 + // 格式化百分比值,去除百分号并除以100
  116 + function formatPercentage(value) {
  117 + if (!value) return '';
  118 + // 检查值是否包含百分号
  119 + if (typeof value === 'string' && value.includes('%')) {
  120 + // 去除百分号并转为数值,然后除以100
  121 + return (parseFloat(value.replace('%', '')) / 100).toString();
  122 + }
  123 + // 如果没有百分号但是是数值,直接除以100
  124 + return value;
  125 + }
  126 +
104 127 async function handleOk() {
105 128 try {
106 129 relationValue.value[0].relationValue = fixCost.value;
107   - relationValue.value[1].relationValue = ratio.value;
108   - relationValue.value[2].relationValue = spainRatio.value;
  130 + relationValue.value[1].relationValue = formatPercentage(ratio.value);
  131 + relationValue.value[2].relationValue = formatPercentage(spainRatio.value);
109 132 await addConfig({
110 133 settingCode: 'customerCode',
111 134 settingName: '客户提成成本配置',
112 135 settingValue: customerCode.value,
113 136 settingType: 3,
114 137 relationCode: 'costSettingItem',
115   - relationName: '成本配置项集合',
  138 + relationName: year.value,
116 139 costSettingItemVOS: relationValue.value,
117 140 });
118 141 emit('modal-success');
... ... @@ -132,6 +155,8 @@
132 155 spainRatio,
133 156 price,
134 157 customerCode,
  158 + year,
  159 + yearOptions,
135 160 customerCodeOptions,
136 161 handleOk,
137 162 };
... ...
src/views/project/config/costEdit.vue
... ... @@ -18,26 +18,30 @@
18 18 <a-input v-model:value="fixCost" addonBefore="固定成本 " />
19 19 <a-input v-model:value="ratio" addonBefore="提成比例 " />
20 20 <a-input v-model:value="spainRatio" addonBefore="西班牙提成比例 " />
  21 + <a-input v-model:value="year" :disabled="true" addonBefore="年份 " />
21 22 <!-- <a-input v-model:value="price" addonBefore="生产提成单价" /> -->
22 23 </a-space>
23 24 </BasicDrawer>
24 25 </template>
25 26 <script lang="ts" setup>
26 27 import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
27   - import { BasicForm, FormSchema, useForm } from '@/components/Form';
28   - import { defineComponent, ref, computed, unref, toRaw, reactive, onMounted } from 'vue';
  28 + import { ref } from 'vue';
29 29 import { saveConfig } from '/@/api/sys/config';
30 30  
31   - // const emit = defineEmits(['success']);
32   - // const isUpdate = ref(true);
33 31 const emit = defineEmits(['success']);
34 32  
35 33 const [register, { closeDrawer }] = useDrawerInner((data) => {
36 34 listAll.value = data.data;
37 35 relationValue.value = JSON.parse(listAll.value.relationValue);
38 36 fixCost.value = relationValue.value[0].relationValue;
39   - ratio.value = relationValue.value[1].relationValue;
40   - spainRatio.value = relationValue.value[2].relationValue;
  37 + // 将小数转换为百分比格式显示
  38 + ratio.value = relationValue.value[1].relationValue
  39 + ? (parseFloat(relationValue.value[1].relationValue) * 100).toFixed(2) + '%'
  40 + : '';
  41 + spainRatio.value = relationValue.value[2].relationValue
  42 + ? (parseFloat(relationValue.value[2].relationValue) * 100).toFixed(2) + '%'
  43 + : '';
  44 + year.value = listAll.value.relationName;
41 45 });
42 46 //获取现有的列表
43 47 const listAll = ref();
... ... @@ -45,12 +49,25 @@
45 49 const ratio = ref();
46 50 const spainRatio = ref();
47 51 const relationValue = ref();
  52 + const year = ref();
  53 +
  54 + // 格式化百分比值,去除百分号并除以100
  55 + function formatPercentage(value) {
  56 + if (!value) return '';
  57 + // 检查值是否包含百分号
  58 + if (typeof value === 'string' && value.includes('%')) {
  59 + // 去除百分号并转为数值,然后除以100
  60 + return (parseFloat(value.replace('%', '')) / 100).toString();
  61 + }
  62 + // 如果没有百分号但是是数值,直接除以100
  63 + return value;
  64 + }
48 65  
49 66 //完成编辑
50 67 async function handleSubmit() {
51 68 relationValue.value[0].relationValue = fixCost.value;
52   - relationValue.value[1].relationValue = ratio.value;
53   - relationValue.value[2].relationValue = spainRatio.value;
  69 + relationValue.value[1].relationValue = formatPercentage(ratio.value);
  70 + relationValue.value[2].relationValue = formatPercentage(spainRatio.value);
54 71 await saveConfig({
55 72 id: listAll.value.id,
56 73 settingCode: 'customerCode',
... ... @@ -58,7 +75,7 @@
58 75 settingValue: listAll.value.settingValue,
59 76 settingType: 3,
60 77 relationCode: 'costSettingItem',
61   - relationName: '成本配置项集合',
  78 + relationName: year.value,
62 79 costSettingItemVOS: relationValue.value,
63 80 });
64 81 emit('success');
... ...
src/views/project/config/data.tsx
1   -import { InputNumber, Tag } from 'ant-design-vue';
  1 +import { Tag } from 'ant-design-vue';
2 2 import { BasicColumn } from '@/components/Table';
3   -import { func } from 'vue-types';
4   -import { h, ref } from 'vue';
5   -
  3 +import { h } from 'vue';
  4 +import { queryNoOptions } from '/@/api/project/order';
6 5  
7 6 export const COLUMNS = {
8 7 1: [
... ... @@ -68,6 +67,11 @@ export const COLUMNS = {
68 67 width: 150,
69 68 },
70 69 {
  70 + title: '年份',
  71 + dataIndex: 'relationName',
  72 + width: 150,
  73 + },
  74 + {
71 75 title: '固定成本',
72 76 dataIndex: 'relationValue',
73 77 width: 150,
... ... @@ -82,7 +86,7 @@ export const COLUMNS = {
82 86 width: 150,
83 87 customRender: (column) => {
84 88 const value = JSON.parse(column.record.relationValue);
85   - return value[1].relationValue;
  89 + return (value[1]?.relationValue * 100).toFixed(2) + '%';
86 90 },
87 91 },
88 92 {
... ... @@ -91,7 +95,7 @@ export const COLUMNS = {
91 95 width: 150,
92 96 customRender: (column) => {
93 97 const value = JSON.parse(column.record.relationValue);
94   - return value[2].relationValue;
  98 + return (value[2]?.relationValue * 100).toFixed(2) + '%';
95 99 },
96 100 },
97 101 // {
... ... @@ -139,6 +143,11 @@ export const COLUMNS = {
139 143 width: 150,
140 144 },
141 145 {
  146 + title: '年份',
  147 + dataIndex: 'relationName',
  148 + width: 150,
  149 + },
  150 + {
142 151 title: '固定成本',
143 152 dataIndex: 'relationValue',
144 153 width: 150,
... ... @@ -148,7 +157,7 @@ export const COLUMNS = {
148 157 },
149 158 },
150 159 {
151   - title: '提成比例',
  160 + title: '提成单价',
152 161 dataIndex: 'relationValue',
153 162 width: 150,
154 163 customRender: (column) => {
... ... @@ -258,3 +267,23 @@ export const columnsProduct: BasicColumn[] = [
258 267 editRow: true,
259 268 },
260 269 ];
  270 +
  271 +export const searchFormSchema = [
  272 + {
  273 + field: 'relationName',
  274 + label: '年份',
  275 + component: 'Select',
  276 + colProps: { span: 8 },
  277 + componentProps: {
  278 + showSearch: true,
  279 + options: (() => {
  280 + const options = [];
  281 + const currentYear = new Date().getFullYear();
  282 + for (let y = 2023; y <= currentYear; y++) {
  283 + options.push({ label: y.toString(), value: y.toString() });
  284 + }
  285 + return options;
  286 + })(),
  287 + },
  288 + },
  289 +]
261 290 \ No newline at end of file
... ...
src/views/project/config/index.vue
... ... @@ -22,7 +22,7 @@
22 22 <TablePanel :searchInfo="{ relationCode: 'orderHodTime' }" :column="5" />
23 23 </Tabs.TabPane>
24 24 <Tabs.TabPane key="6" tab="提成成本配置">
25   - <TablePanel :searchInfo="{ relationCode: 'costSettingItem' }" :column="6" />
  25 + <TablePanel :searchInfo="{ relationCode: 'costSettingItem', relationName: currentYear }" :column="6" />
26 26 </Tabs.TabPane>
27 27 <Tabs.TabPane key="7" tab="销售额配置" v-if="role !== ROLE.FINANCE">
28 28 <TablePanel :searchInfo="{ relationCode: 'salesAmount' }" :column="7" />
... ... @@ -31,7 +31,7 @@
31 31 <TablePanel :searchInfo="{ relationCode: 'produHodTime' }" :column="8" />
32 32 </Tabs.TabPane>
33 33 <Tabs.TabPane key="9" tab="生产科固定成本">
34   - <TablePanel :searchInfo="{ relationCode: 'ProduceSettingItem' }" :column="9" />
  34 + <TablePanel :searchInfo="{ relationCode: 'ProduceSettingItem', relationName: currentYear }" :column="9" />
35 35 </Tabs.TabPane>
36 36 <Tabs.TabPane key="10" tab="客户公司" v-if="role !== ROLE.FINANCE">
37 37 <TablePanel :searchInfo="{ relationCode: 'companyConfiguration' }" :column="10" />
... ... @@ -56,6 +56,7 @@
56 56 const orderStore = useOrderStoreWithOut();
57 57 const userStore = useUserStoreWithOut();
58 58 const user = userStore.getUserInfo;
  59 + const currentYear = new Date().getFullYear().toString();
59 60 const role = computed(() => {
60 61 return user?.roleSmallVO?.code;
61 62 });
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/ApproveReason.vue
... ... @@ -32,7 +32,9 @@
32 32 orderId: baseFieldValues.value.orderId,
33 33 productionActualPrice: baseFieldValues.value.productionActualPrice,
34 34 productionDepartmentPredictPrice: baseFieldValues.value.productionDepartmentPredictPrice,
35   - type: 70,
  35 + productionDepartmentPredictUnitPrice: baseFieldValues.value.productionDepartmentPredictUnitPrice,
  36 + applyRemark: baseFieldValues.value.applyRemark,
  37 + type: 90,
36 38 });
37 39 emit('success');
38 40 setTimeout(() => {
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/FinanceEdit.vue
... ... @@ -9,6 +9,7 @@
9 9 @ok="handleSubmit"
10 10 :showDetailBack="false"
11 11 okText="保存"
  12 + @visible-change="handleShow"
12 13 showFooter
13 14 :destroyOnClose="true"
14 15 >
... ... @@ -19,7 +20,7 @@
19 20 <a-input
20 21 v-model:value="input1"
21 22 placeholder="请输入"
22   - :disabled="status1 === 'LOCKED'"
  23 + :disabled="status1 == 'LOCKED'"
23 24 auto-size
24 25 />
25 26 <div style="margin: 16px 0"></div>
... ... @@ -27,7 +28,15 @@
27 28 <a-input
28 29 v-model:value="input2"
29 30 placeholder="请输入"
30   - :disabled="status2 === 'LOCKED'"
  31 + :disabled="status2 == 'LOCKED'"
  32 + auto-size
  33 + />
  34 + <div style="margin: 16px 0"></div>
  35 + <div style="font-size: 15px">生产科预算单价</div>
  36 + <a-input
  37 + v-model:value="input3"
  38 + placeholder="请输入"
  39 + :disabled="status3 == 'LOCKED'"
31 40 auto-size
32 41 />
33 42 <div style="margin: 16px 0"></div>
... ... @@ -42,7 +51,7 @@
42 51 <script lang="ts" setup>
43 52 import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
44 53 import { BasicForm, FormSchema, useForm } from '@/components/Form';
45   - import { defineComponent, ref, computed, unref, toRaw, reactive } from 'vue';
  54 + import { defineComponent, ref, computed, watch, toRaw, reactive } from 'vue';
46 55 import { getPackageEdit } from '@/api/project/invoice';
47 56 import { useMessage } from '/@/hooks/web/useMessage';
48 57 import { ROLE } from './type.d';
... ... @@ -125,25 +134,37 @@
125 134 const update = ref();
126 135 const status1 = ref();
127 136 const status2 = ref();
  137 + const status3 = ref();
128 138  
129 139 const input1 = ref();
130 140 const input2 = ref();
  141 + const input3 = ref();
  142 + const orderCount = ref();
131 143 const id = ref();
  144 + watch(input3, (newVal) => {
  145 + if (newVal && orderCount.value) {
  146 + input1.value = (parseFloat(newVal) * parseFloat(orderCount.value)).toFixed(2);
  147 + } else {
  148 + input1.value = '';
  149 + }
  150 + });
132 151 const [register, { setDrawerProps, closeDrawer }] = useDrawerInner((data) => {
133 152 // 方式1
134   - if (data.data.lockFields) {
135   - status1.value = data.data?.productionDepartmentPredictPrice;
136   - status2.value = data.data?.productionActualPrice;
  153 + if (data.data?.lockFields) {
  154 + status1.value = data.data?.lockFields?.productionDepartmentPredictPrice;
  155 + status2.value = data.data?.lockFields?.productionActualPrice;
  156 + status3.value = data.data?.lockFields?.productionDepartmentPredictUnitPrice;
137 157 }
138   -
139 158 id.value = data.data.orderId;
140   - input1.value = data.data?.productionDepartmentPredictPrice.toFixed(2);
141   - input2.value = data.data?.productionActualPrice.toFixed(2);
  159 + input1.value = data.data?.productionDepartmentPredictPrice?.toFixed(2);
  160 + input2.value = data.data?.productionActualPrice?.toFixed(2);
  161 + orderCount.value = data.data?.orderCount;
  162 + input3.value = data.data?.productionDepartmentPredictUnitPrice?.toFixed(2);
142 163 resetFields();
143 164 setDrawerProps({ confirmLoading: false });
144   - setFieldsValue({
145   - ...toRaw(data.data),
146   - });
  165 + // setFieldsValue({
  166 + // ...toRaw(data.data),
  167 + // });
147 168 update.value = data;
148 169 });
149 170 //完成编辑
... ... @@ -154,16 +175,25 @@
154 175 // id: update.value.data.id,
155 176 // bgUrl: update.value.data.bgUrl,
156 177 // };
157   - if (!input1.value || !input2.value) {
  178 + if (!input1.value || !input2.value || !input3.value) {
158 179 error('选项不能为空');
159 180 } else {
160 181 await getPackageEdit({
161 182 orderId: id.value,
162 183 productionDepartmentPredictPrice: input1.value,
163 184 productionActualPrice: input2.value,
  185 + orderCount: orderCount.value,
  186 + productionDepartmentPredictUnitPrice: input3.value,
164 187 });
165 188 emit('success');
166 189 closeDrawer();
167 190 }
168 191 }
  192 + function handleShow(visible: boolean) {
  193 + if (!visible) {
  194 + input1.value = '';
  195 + input2.value = '';
  196 + input3.value = '';
  197 + }
  198 + }
169 199 </script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/HistoryDetail.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + @register="register"
  4 + v-bind="$attrs"
  5 + title="操作记录"
  6 + width="60%"
  7 + :isDetail="true"
  8 + :showDetailBack="false"
  9 + okText="保存"
  10 + :destroyOnClose="true"
  11 + >
  12 + <Tabs v-model:activeKey="activeKey" className="my-0">
  13 + <TabPanel :key="1" tab="操作记录" className="w-full">
  14 + <a-list :pagination="pagination1" className="w-full">
  15 + <template v-for="item in list1" :key="item.id">
  16 + <a-list-item class="list">
  17 + <a-list-item-meta>
  18 + <template #avatar> </template>
  19 + <template #title>
  20 + <span>{{ item.userName }}</span>
  21 + </template>
  22 + <template #description>
  23 + <div class="description">
  24 + {{ item.optType }}
  25 + </div>
  26 + <div class="info">
  27 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  28 + </div>
  29 + </template>
  30 + </a-list-item-meta>
  31 + </a-list-item>
  32 + </template>
  33 + </a-list>
  34 + </TabPanel>
  35 + <TabPanel :key="2" tab="审批记录" className="w-full">
  36 + <a-list :pagination="pagination2" className="w-full">
  37 + <template v-for="item in list2" :key="item.id">
  38 + <a-list-item class="list">
  39 + <a-list-item-meta>
  40 + <template #avatar> </template>
  41 + <template #title>
  42 + <span>{{ item.userName }}</span>
  43 + </template>
  44 + <template #description>
  45 + <div class="description">
  46 + {{ item.optType }}
  47 + </div>
  48 + <div class="info">
  49 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  50 + </div>
  51 + </template>
  52 + </a-list-item-meta>
  53 + </a-list-item>
  54 + </template>
  55 + </a-list>
  56 + </TabPanel>
  57 + </Tabs>
  58 +
  59 + <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
  60 + <template #appendFooter>
  61 + <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
  62 + </template>
  63 + </BasicDrawer>
  64 +</template>
  65 +<script lang="ts">
  66 + import { defineComponent, ref, computed } from 'vue';
  67 + import { Tabs, List } from 'ant-design-vue';
  68 + import { FormSchema } from '/@/components/Form/index';
  69 +
  70 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  71 + import { getOrderCostDetailedOptLog } from '/@/api/project/invoice';
  72 + import { formatToDateTime } from '/@/utils/dateUtil';
  73 +
  74 + const TabPanel = Tabs.TabPane;
  75 +
  76 + const schemas: FormSchema[] = [
  77 + {
  78 + field: '订单号',
  79 + component: 'Input',
  80 + label: '字段1',
  81 + componentProps: {
  82 + readonly: true,
  83 + disabled: true,
  84 + },
  85 + colProps: {
  86 + span: 12,
  87 + },
  88 + defaultValue: '111',
  89 + },
  90 + {
  91 + field: 'field2',
  92 + component: 'Input',
  93 + label: '字段2',
  94 + colProps: {
  95 + span: 12,
  96 + },
  97 + },
  98 + ];
  99 + const achieveList = [
  100 + {
  101 + key: '1',
  102 + name: '操作记录',
  103 + },
  104 + {
  105 + key: '2',
  106 + name: '审批记录',
  107 + },
  108 + ];
  109 + export default defineComponent({
  110 + components: {
  111 + BasicDrawer,
  112 + Tabs,
  113 + TabPanel,
  114 + [List.name]: List,
  115 + [List.Item.name]: List.Item,
  116 + AListItemMeta: List.Item.Meta,
  117 + },
  118 + props: {
  119 + onGoCheckDetail: {
  120 + type: Function,
  121 + },
  122 + },
  123 + setup() {
  124 + const list1 = ref([]);
  125 + const total1 = ref(0);
  126 + const page1 = ref(1);
  127 +
  128 + const list2 = ref([]);
  129 + const total2 = ref(0);
  130 + const page2 = ref(1);
  131 + const orderId = ref('');
  132 + const activeKey = ref(1);
  133 +
  134 + const getOrderOptLogFunc = async (data, index, page) => {
  135 + console.log('%c [ data ]-135', 'font-size:13px; background:pink; color:#bf2c9f;', data);
  136 + if (index === 1) {
  137 + const res = await getOrderCostDetailedOptLog({
  138 + orderId: data,
  139 + type: [40],
  140 + page: page,
  141 + pageSize: 20,
  142 + });
  143 + list1.value = res.items;
  144 + total1.value = res.total;
  145 + page1.value = page;
  146 + } else {
  147 + const res = await getOrderCostDetailedOptLog({
  148 + orderId: data,
  149 + type: [90,91],
  150 + page: page,
  151 + pageSize: 20,
  152 + });
  153 + list2.value = res.items;
  154 + total2.value = res.total;
  155 + page2.value = page;
  156 + }
  157 + };
  158 + const [register] = useDrawerInner((data) => {
  159 + orderId.value = data.data.orderId;
  160 + getOrderOptLogFunc(orderId.value, 1, 1);
  161 + getOrderOptLogFunc(orderId.value, 2, 1);
  162 + });
  163 +
  164 + const pagination1 = computed(() => {
  165 + return {
  166 + show: true,
  167 + pageSize: 20,
  168 + page: page1.value,
  169 + total: total1.value,
  170 + onChange(cur) {
  171 + getOrderOptLogFunc(orderId.value, 1, cur);
  172 + },
  173 + };
  174 + });
  175 +
  176 + const pagination2 = computed(() => {
  177 + return {
  178 + show: true,
  179 + pageSize: 20,
  180 + page: page1.value,
  181 + total: total1.value,
  182 + onChange(cur) {
  183 + getOrderOptLogFunc(orderId.value, 2, cur);
  184 + },
  185 + };
  186 + });
  187 +
  188 + return {
  189 + register,
  190 + schemas,
  191 + achieveList,
  192 + list1,
  193 + list2,
  194 + prefixCls: 'account-center',
  195 + pagination1,
  196 + pagination2,
  197 + activeKey,
  198 + formatToDateTime,
  199 + };
  200 + },
  201 + });
  202 +</script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/data.tsx
... ... @@ -137,7 +137,15 @@ export const COLUMNS = [
137 137 },
138 138 },
139 139 {
140   - title: '生产科预算金额¥',
  140 + title: '生产科预算单价¥',
  141 + width: 150,
  142 + dataIndex: 'productionDepartmentPredictUnitPrice',
  143 + customRender: (column) => {
  144 + return column.record?.productionDepartmentPredictUnitPrice?.toFixed(2);
  145 + },
  146 + },
  147 + {
  148 + title: '生产科预算总金额¥',
141 149 width: 150,
142 150 dataIndex: 'productionDepartmentPredictPrice',
143 151 customRender: (column) => {
... ... @@ -145,9 +153,9 @@ export const COLUMNS = [
145 153 },
146 154 },
147 155 {
148   - title: '实际发生费用',
  156 + title: '生产科实际花费总金额¥',
149 157 dataIndex: 'productionActualPrice',
150   - width: 120,
  158 + width: 180,
151 159 customRender: (column) => {
152 160 return column.record?.productionActualPrice?.toFixed(2);
153 161 },
... ... @@ -158,7 +166,7 @@ export const COLUMNS = [
158 166 width: 120,
159 167 customRender: (column) => {
160 168 if (column.record?.predictRatio) {
161   - return column.record?.predictRatio?.toFixed(2) + '%';
  169 + return (column.record?.predictRatio * 100).toFixed(2) + '%';
162 170 }
163 171 return column.record?.predictRatio?.toFixed(2);
164 172 },
... ... @@ -169,7 +177,7 @@ export const COLUMNS = [
169 177 width: 120,
170 178 customRender: (column) => {
171 179 if (column.record?.predictAndActualRatio) {
172   - return column.record?.predictAndActualRatio?.toFixed(2) + '%';
  180 + return (column.record?.predictAndActualRatio * 100).toFixed(2) + '%';
173 181 }
174 182 return column.record?.predictAndActualRatio?.toFixed(2);
175 183 },
... ... @@ -188,7 +196,7 @@ export const COLUMNS = [
188 196 width: 120,
189 197 customRender: (column) => {
190 198 if (column.record?.beforeGrossProfitRate) {
191   - return column.record?.beforeGrossProfitRate?.toFixed(2) + '%';
  199 + return (column.record?.beforeGrossProfitRate * 100).toFixed(2) + '%';
192 200 }
193 201 return column.record?.beforeGrossProfitRate?.toFixed(2);
194 202 },
... ... @@ -207,11 +215,25 @@ export const COLUMNS = [
207 215 width: 120,
208 216 customRender: (column) => {
209 217 if (column.record?.grossProfitRate) {
210   - return column.record?.grossProfitRate?.toFixed(2) + '%';
  218 + return (column.record?.grossProfitRate * 100).toFixed(2) + '%';
211 219 }
212 220 return column.record?.grossProfitRate?.toFixed(2);
213 221 },
214 222 },
  223 + {
  224 + title: '状态',
  225 + dataIndex: 'status',
  226 + width: 120,
  227 + customRender: (column) => {
  228 + if (column.record?.innerProduceStatus === null || column.record?.innerProduceStatus === -1) {
  229 + return '未完成';
  230 + } else if (column.record?.innerProduceStatus === 0) {
  231 + return '待审核';
  232 + } else if (column.record?.innerProduceStatus === 1) {
  233 + return '已审核';
  234 + }
  235 + },
  236 + },
215 237 // {
216 238 // title: '内部生产固定成本¥',
217 239 // dataIndex: 'innerProductionFixedCost',
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/index.vue
1 1 <template>
2 2 <div>
3 3 <BasicTable @register="registerTable" :bordered="true">
  4 + <template #headerTop>
  5 + <a-alert type="info" show-icon>
  6 + <template #message>
  7 + <template v-if="checkedKeys.length > 0">
  8 + <span>已选中{{ checkedKeys.length }}条记录(可跨页)</span>
  9 + <a-button
  10 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  11 + type="link"
  12 + @click="handleClearChoose"
  13 + size="small"
  14 + >清空</a-button
  15 + >
  16 + </template>
  17 + <template v-else>
  18 + <span>未选中任何订单</span>
  19 + </template>
  20 + </template>
  21 + </a-alert>
  22 + </template>
4 23 <template #bodyCell="{ column, record }">
5 24 <template v-if="column.key === 'picUrl'">
6 25 <img :z-index="100000" :width="50" :height="50" :src="record?.picUrl" />
7 26 </template>
8 27 <template v-if="column.key === 'action'">
9   - <TableAction :actions="createActions(record)" />
  28 + <TableAction :actions="createActions(record)" :dropDownActions="createDropActions(record)" />
10 29 </template>
11 30 <!-- <template v-if="column.key === 'relationValue'">
12 31 <a-input
... ... @@ -19,6 +38,15 @@
19 38 </span>
20 39 </template> -->
21 40 </template>
  41 + <template #toolbar>
  42 + <a-button
  43 + type="primary"
  44 + @click="handleExport"
  45 + v-if="role == ROLE.ADMIN || role == ROLE.FINANCE"
  46 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  47 + >导出</a-button
  48 + >
  49 + </template>
22 50 </BasicTable>
23 51 <!-- <BasicModal
24 52 title="拒绝原因"
... ... @@ -32,28 +60,48 @@
32 60 <a-textarea :rows="6" placeholder="请输入拒绝原因" v-model:value="message" />
33 61 </div>
34 62 </BasicModal> -->
35   - <CheckDetail @register="checkModalRegister" />
  63 + <CheckDetail @register="checkModalRegister" :onGoFormDetail="handleGoFormDetail" />
36 64 <FinanceEdit @register="registerFinanceEdit" @success="handleSuccess" />
  65 + <HistoryDetail @register="registerHistoryDetail" />
37 66 </div>
38 67 </template>
39 68 <script setup lang="ts">
40 69 import { BasicTable, useTable, TableAction } from '/@/components/Table';
41   - import { getInnerProfit } from '@/api/project/invoice';
  70 + import { getInnerProfit, setInnerStatus } from '@/api/project/invoice';
42 71 import { searchFormSchema, COLUMNS } from './data';
43 72 import { BasicModal, useModal } from '/@/components/Modal';
44 73 import { useMessage } from '/@/hooks/web/useMessage';
45   - import { onMounted, ref } from 'vue';
  74 + import { onMounted, ref, computed } from 'vue';
  75 + import axios from 'axios';
46 76 import { useDrawer } from '/@/components/Drawer';
47 77 import FinanceEdit from './FinanceEdit.vue';
48 78 import CheckDetail from './CheckDetail.vue';
  79 + import HistoryDetail from './HistoryDetail.vue';
49 80 import { useOrderStoreWithOut } from '/@/store/modules/order';
  81 + import { useUserStoreWithOut } from '/@/store/modules/user';
  82 + import { RoleEnum } from '/@/enums/roleEnum';
  83 + import { ROLE } from '../../../type.d';
50 84  
51 85 const { createMessage } = useMessage();
52 86 const { error } = createMessage;
53 87 const message = ref();
  88 + const checkedKeys = ref<string[]>([]);
  89 + // Define arrays to store collected data from selected rows
  90 + const orderIds = ref<number[]>([]);
  91 + const projectNo = ref<string[]>([]);
  92 + const customerCode = ref<string[]>([]);
  93 + const innerNo = ref<string[]>([]);
  94 + const productionDepartment = ref<string[]>([]);
  95 + const detailProjectNoKeys = ref<string[]>([]);
54 96 const orderStore = useOrderStoreWithOut();
55 97 const [checkModalRegister, { openDrawer: openCheckDetailDrawer }] = useDrawer();
56 98 const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer();
  99 + const [registerHistoryDetail, { openDrawer: openHistoryDetail }] = useDrawer();
  100 + const userStore = useUserStoreWithOut();
  101 + const user = userStore.getUserInfo;
  102 + const role = computed(() => {
  103 + return user?.roleSmallVO?.code;
  104 + });
57 105 const handleOk = (record) => {
58 106 // 修改父组件的状态
59 107 closeModal();
... ... @@ -63,11 +111,28 @@
63 111 message.value = '';
64 112 }
65 113 };
66   - const [registerTable, { reload }] = useTable({
  114 + const [registerTable, { reload, setSelectedRowKeys, getForm }] = useTable({
67 115 api: getInnerProfit,
68 116 bordered: true,
69 117 columns: COLUMNS,
70   - rowKey: 'id',
  118 + rowKey: 'orderId',
  119 + customRow: () => {
  120 + return {
  121 + onClick: (e) => {
  122 + // Prevent row selection when clicking anywhere except checkboxes
  123 + if (!e.target.closest('.ant-checkbox-wrapper')) {
  124 + e.stopPropagation();
  125 + }
  126 + },
  127 + };
  128 + },
  129 + rowSelection: {
  130 + type: 'checkbox',
  131 + selectedRowKeys: checkedKeys as any,
  132 + onSelect: onSelect,
  133 + onSelectAll: onSelectAll,
  134 + checkStrictly: true,
  135 + },
71 136 formConfig: {
72 137 labelWidth: 120,
73 138 schemas: searchFormSchema,
... ... @@ -80,7 +145,7 @@
80 145 setting: false,
81 146 },
82 147 actionColumn: {
83   - width: 240,
  148 + width: 260,
84 149 title: 'Action',
85 150 dataIndex: 'action',
86 151 },
... ... @@ -111,6 +176,18 @@
111 176 // },
112 177 onClick: handleFalse.bind(null, record),
113 178 },
  179 + {
  180 + label: '审批通过',
  181 + popConfirm: {
  182 + title: '确认审批?',
  183 + confirm: () => {
  184 + if (record.innerProduceStatus === 0 && role.value === ROLE.ADMIN) {
  185 + handleStatus(record, true);
  186 + }
  187 + return;
  188 + },
  189 + },
  190 + },
114 191 ];
115 192 }
116 193 return [
... ... @@ -128,6 +205,18 @@
128 205 ];
129 206 }
130 207  
  208 + function createDropActions(record: any) {
  209 + if (!record.editable) {
  210 + const actions = [
  211 + {
  212 + label: '历史记录',
  213 + onClick: handleHistoryDetail.bind(null, record),
  214 + },
  215 + ];
  216 + return actions;
  217 + }
  218 + }
  219 +
131 220 onMounted(async () => {
132 221 await orderStore.getDict();
133 222 });
... ... @@ -175,5 +264,224 @@
175 264 reload();
176 265 }, 50);
177 266 }
  267 +
  268 + async function handleStatus(record, status) {
  269 + try {
  270 + // Add your API call here for updating status
  271 + // Example: await updateStatus({ id: record.id, status: status ? 1 : 2 });
  272 + await setInnerStatus({ orderId: record.orderId });
  273 + reload();
  274 + } catch (error) {
  275 + console.error(error);
  276 + }
  277 + }
  278 +
  279 + function handleHistoryDetail(record) {
  280 + openHistoryDetail(true, {
  281 + data: record,
  282 + });
  283 + }
  284 +
  285 + function handleGoFormDetail() {
  286 + reload();
  287 + }
  288 +
  289 + function handleClearChoose() {
  290 + checkedKeys.value = [];
  291 + orderIds.value = [];
  292 + projectNo.value = [];
  293 + customerCode.value = [];
  294 + innerNo.value = [];
  295 + productionDepartment.value = [];
  296 + detailProjectNoKeys.value = [];
  297 + setSelectedRowKeys([]);
  298 + }
  299 +
  300 + function handleExport() {
  301 + // Get current search parameters from the form
  302 + const values = getForm().getFieldsValue();
  303 +
  304 + // Create export parameters based on whether orderIds are selected
  305 + let exportParams;
  306 +
  307 + // If orderIds are selected, only use those and ignore search params
  308 + if (orderIds.value.length > 0) {
  309 + exportParams = {
  310 + orderIds: orderIds.value
  311 + };
  312 + }
  313 + // Otherwise use the search parameters
  314 + else {
  315 + exportParams = {
  316 + ...values,
  317 + projectNo: values.projectNo || undefined,
  318 + customerCode: values.customerCode || undefined,
  319 + innerNo: values.innerNo || undefined,
  320 + productionDepartment: values.productionDepartment || undefined
  321 + };
  322 + }
  323 +
  324 +
  325 + const token = userStore.getToken;
  326 + axios
  327 + .post(
  328 + '/basic-api/order/cost/innerProfitDetail/exportExcel',
  329 + exportParams,
  330 + {
  331 + headers: {
  332 + Authorization: `${token}`,
  333 + },
  334 + responseType: 'blob',
  335 + },
  336 + )
  337 + .then((response) => {
  338 + // 创建一个 Blob 对象来保存二进制数据
  339 + const blob = new Blob([response.data], { type: 'application/zip' });
  340 + const getFormattedDate = (): string => {
  341 + const date = new Date();
  342 +
  343 + const year = date.getFullYear();
  344 + const month = String(date.getMonth() + 1).padStart(2, '0');
  345 + const day = String(date.getDate()).padStart(2, '0');
  346 +
  347 + const hours = String(date.getHours()).padStart(2, '0');
  348 + const minutes = String(date.getMinutes()).padStart(2, '0');
  349 + const seconds = String(date.getSeconds()).padStart(2, '0');
  350 +
  351 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  352 + };
  353 + const date = getFormattedDate();
  354 + // 创建一个链接元素用于下载
  355 + const link = document.createElement('a');
  356 + link.href = window.URL.createObjectURL(blob);
  357 + link.download = `内研数据筛选净利润分析表${date}.xlsx`;
  358 + document.body.appendChild(link);
  359 + link.click(); // 自动点击链接,触发下载
  360 + document.body.removeChild(link); // 下载完成后移除链接
  361 + })
  362 + .catch((error) => {
  363 + console.error(error);
  364 + });
  365 + handleClearChoose();
  366 + reload();
  367 + }
  368 +
  369 + async function onSelect(record, selected: boolean) {
  370 + const rowKey = record.orderId;
  371 + if (selected) {
  372 + checkedKeys.value = [...checkedKeys.value, rowKey];
  373 +
  374 + // Add data from the selected row to the corresponding arrays
  375 + if (record.orderId !== undefined && !orderIds.value.includes(record.orderId)) {
  376 + orderIds.value.push(record.orderId);
  377 + }
  378 +
  379 + if (record.projectNo !== undefined && !projectNo.value.includes(record.projectNo)) {
  380 + projectNo.value.push(record.projectNo);
  381 + }
  382 +
  383 + if (record.customerCode !== undefined && !customerCode.value.includes(record.customerCode)) {
  384 + customerCode.value.push(record.customerCode);
  385 + }
  386 +
  387 + if (record.innerNo !== undefined && !innerNo.value.includes(record.innerNo)) {
  388 + innerNo.value.push(record.innerNo);
  389 + }
  390 +
  391 + if (record.productionDepartment !== undefined && !productionDepartment.value.includes(record.productionDepartment)) {
  392 + productionDepartment.value.push(record.productionDepartment);
  393 + }
  394 +
  395 + if (record.detailProjectNo !== undefined && !detailProjectNoKeys.value.includes(record.detailProjectNo)) {
  396 + detailProjectNoKeys.value.push(record.detailProjectNo);
  397 + }
  398 + } else {
  399 + checkedKeys.value = checkedKeys.value.filter((key) => key !== rowKey);
  400 +
  401 + // Remove data from the deselected row from the corresponding arrays
  402 + if (record.orderId !== undefined) {
  403 + orderIds.value = orderIds.value.filter((id) => id !== record.orderId);
  404 + }
  405 +
  406 + if (record.projectNo !== undefined) {
  407 + projectNo.value = projectNo.value.filter((no) => no !== record.projectNo);
  408 + }
  409 +
  410 + if (record.customerCode !== undefined) {
  411 + customerCode.value = customerCode.value.filter((code) => code !== record.customerCode);
  412 + }
  413 +
  414 + if (record.innerNo !== undefined) {
  415 + innerNo.value = innerNo.value.filter((no) => no !== record.innerNo);
  416 + }
  417 +
  418 + if (record.productionDepartment !== undefined) {
  419 + productionDepartment.value = productionDepartment.value.filter(
  420 + (dept) => dept !== record.productionDepartment,
  421 + );
  422 + }
  423 +
  424 + if (record.detailProjectNo !== undefined) {
  425 + detailProjectNoKeys.value = detailProjectNoKeys.value.filter(
  426 + (projectNo) => projectNo !== record.detailProjectNo,
  427 + );
  428 + }
  429 + }
  430 + setSelectedRowKeys(checkedKeys.value as any);
  431 + }
  432 +
  433 + function onSelectAll(selected: boolean, selectedRows: any[]) {
  434 + if (selected) {
  435 + // First clear all previous selections to avoid duplicates
  436 + checkedKeys.value = [];
  437 + orderIds.value = [];
  438 + projectNo.value = [];
  439 + customerCode.value = [];
  440 + innerNo.value = [];
  441 + productionDepartment.value = [];
  442 + detailProjectNoKeys.value = [];
  443 +
  444 + // Add all selected rows
  445 + selectedRows.forEach((row) => {
  446 + const rowKey = row.orderId;
  447 + checkedKeys.value.push(rowKey);
  448 +
  449 + // Add data from each selected row to the corresponding arrays
  450 + if (row.orderId !== undefined) {
  451 + orderIds.value.push(row.orderId);
  452 + }
  453 +
  454 + if (row.projectNo !== undefined) {
  455 + projectNo.value.push(row.projectNo);
  456 + }
  457 +
  458 + if (row.customerCode !== undefined) {
  459 + customerCode.value.push(row.customerCode);
  460 + }
  461 +
  462 + if (row.innerNo !== undefined) {
  463 + innerNo.value.push(row.innerNo);
  464 + }
  465 +
  466 + if (row.productionDepartment !== undefined) {
  467 + productionDepartment.value.push(row.productionDepartment);
  468 + }
  469 +
  470 + if (row.detailProjectNo !== undefined) {
  471 + detailProjectNoKeys.value.push(row.detailProjectNo);
  472 + }
  473 + });
  474 + } else {
  475 + // Clear all selections when deselecting all
  476 + checkedKeys.value = [];
  477 + orderIds.value = [];
  478 + projectNo.value = [];
  479 + customerCode.value = [];
  480 + innerNo.value = [];
  481 + productionDepartment.value = [];
  482 + detailProjectNoKeys.value = [];
  483 + }
  484 + setSelectedRowKeys(checkedKeys.value as any);
  485 + }
178 486 </script>
179 487 <style></style>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerData/tableData.tsx
... ... @@ -34,6 +34,13 @@ export const FIELDS_BASE_INFO = [
34 34 label: '实际发生费用',
35 35 rules: [{ required: true }],
36 36 },
  37 + {
  38 + field: 'productionDepartmentPredictUnitPrice',
  39 + component: 'Select',
  40 + labelWidth: 150,
  41 + label: '生产科预算单价',
  42 + rules: [{ required: true }],
  43 + },
37 44 ];
38 45  
39 46 export const EDIT_INFO = [
... ... @@ -58,5 +65,12 @@ export const EDIT_INFO = [
58 65 label: '实际发生费用',
59 66 rules: [{ required: true }],
60 67 },
  68 + {
  69 + field: 'productionDepartmentPredictUnitPrice',
  70 + component: 'Select',
  71 + labelWidth: 150,
  72 + label: '生产科预算单价',
  73 + rules: [{ required: true }],
  74 + },
61 75 ];
62 76  
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/ApproveReason.vue 0 → 100644
  1 +<template>
  2 + <BasicModal
  3 + v-bind="$attrs"
  4 + destroyOnClose
  5 + @register="register"
  6 + title="申请原因"
  7 + :helpMessage="['提示1', '提示2']"
  8 + @open-change="handleShow"
  9 + :bodyStyle="{ height: '200px' }"
  10 + @ok="handleOk"
  11 + z-index="9999"
  12 + >
  13 + <a-textarea v-model:value="input" :rows="7" />
  14 + </BasicModal>
  15 +</template>
  16 +<script lang="ts" setup>
  17 + import { ref, watch } from 'vue';
  18 + import { BasicModal, useModalInner } from '@/components/Modal';
  19 + import { orderAuth } from '/@/api/project/order';
  20 + import { getServiceApplyEdit } from '/@/api/project/invoice';
  21 +
  22 + const emit = defineEmits(['success']);
  23 + const input = ref('');
  24 + const baseFieldValues = ref();
  25 + const [register, { setModalProps, redoModalHeight, closeModal }] = useModalInner(async (data) => {
  26 + baseFieldValues.value = data.data;
  27 + baseFieldValues.value.projectNoPrefix = data.id;
  28 + });
  29 + async function handleOk() {
  30 + baseFieldValues.value.applyRemark = input.value;
  31 + await getServiceApplyEdit({
  32 + projectNoPrefix: baseFieldValues.value.projectNoPrefix,
  33 + projectInnerProfitInfoEndTime: baseFieldValues.value.projectInnerProfitInfoEndTime,
  34 + projectInnerProfitInfoStartTime: baseFieldValues.value.projectInnerProfitInfoStartTime,
  35 + applyRemark: baseFieldValues.value.applyRemark,
  36 + type: 'INNER_PROFIT_FIELD_EDIT_APPLY',
  37 + });
  38 + emit('success');
  39 + setTimeout(() => {
  40 + closeModal();
  41 + }, 50);
  42 + }
  43 + function handleShow() {
  44 + input.value = '';
  45 + closeModal();
  46 + }
  47 +</script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/CheckDetail.vue 0 → 100644
  1 +<template>
  2 + <div class="container">
  3 + <BasicDrawer
  4 + @register="register"
  5 + v-bind="$attrs"
  6 + showFooter
  7 + title="字段编辑权限申请"
  8 + width="60%"
  9 + :destroyOnClose="true"
  10 + :isDetail="true"
  11 + @ok="handleSubmit"
  12 + :showDetailBack="false"
  13 + okText="申请"
  14 + ><input />
  15 + <div>
  16 + <template v-if="role === ROLE.ADMIN || role === ROLE.FINANCE">
  17 + <BasicForm @register="registerForm" />
  18 + </template>
  19 + </div>
  20 + <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
  21 +
  22 + <!-- <template #appendFooter>
  23 + <a-button type="primary" @click="onGoFormDetail"> 返回编辑</a-button>
  24 + </template> -->
  25 + </BasicDrawer>
  26 + <ApproveReason @register="approveReasonRegister" @success="handleCloseModal" />
  27 + </div>
  28 +</template>
  29 +<script lang="ts">
  30 + import { computed, defineComponent, reactive, ref } from 'vue';
  31 + import { BasicForm, useForm } from '/@/components/Form/index';
  32 + import { getPackageApplyEdit } from '/@/api/project/invoice';
  33 + import { ROLE } from '../../../financeList/type.d';
  34 + import { useModal } from '/@/components/Modal';
  35 + import ApproveReason from './ApproveReason.vue';
  36 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  37 + import { FIELDS_BASE_INFO } from './tableData';
  38 + import { useUserStoreWithOut } from '/@/store/modules/user';
  39 +
  40 + const userStore = useUserStoreWithOut();
  41 + const getSchema = (fields) =>
  42 + fields.map((item) => ({
  43 + field: `${item.field}`,
  44 + dataIndex: `${item.field}`,
  45 + label: item.label,
  46 + component: 'Switch',
  47 + componentProps: {
  48 + checkedValue: 'UN_LOCKED',
  49 + unCheckedValue: 'LOCKED',
  50 + },
  51 + colProps: {
  52 + span: 8,
  53 + },
  54 + }));
  55 +
  56 + export default defineComponent({
  57 + components: { BasicDrawer, BasicForm, ApproveReason },
  58 + props: {
  59 + onGoFormDetail: {
  60 + type: Function,
  61 + },
  62 + },
  63 + setup() {
  64 + const id = ref('');
  65 + const schemas = getSchema(FIELDS_BASE_INFO);
  66 + const [registerForm, { getFieldsValue }] = useForm({
  67 + labelWidth: 180,
  68 + schemas,
  69 + showActionButtonGroup: false,
  70 + actionColOptions: {
  71 + span: 24,
  72 + },
  73 + });
  74 + const [approveReasonRegister, { openModal: openApproveReasonDrawer }] = useModal();
  75 +
  76 + const lockFields = reactive({});
  77 + const [register, { closeDrawer }] = useDrawerInner((data) => {
  78 + Object.assign(lockFields, data.lockFields);
  79 + id.value = data.projectNoPrefix;
  80 + });
  81 + function handleCloseModal() {
  82 + closeDrawer();
  83 + }
  84 +
  85 + const role = computed(() => {
  86 + return userStore.getUserInfo?.roleSmallVO?.code;
  87 + });
  88 +
  89 + const handleSubmit = async () => {
  90 + const baseFieldValues = getFieldsValue();
  91 + openApproveReasonDrawer(true, {
  92 + data: baseFieldValues,
  93 + id: id.value,
  94 + });
  95 + };
  96 +
  97 + return {
  98 + register,
  99 + schemas,
  100 + registerForm,
  101 + handleSubmit,
  102 + handleCloseModal,
  103 + approveReasonRegister,
  104 + openApproveReasonDrawer,
  105 + ROLE,
  106 + role,
  107 + };
  108 + },
  109 + });
  110 +</script>
  111 +<style>
  112 + .container {
  113 + position: fixed; /* 或 absolute, fixed */
  114 + z-index: 10;
  115 + }
  116 +</style>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/FinanceEdit.vue
... ... @@ -3,31 +3,57 @@
3 3 <BasicDrawer
4 4 @register="register"
5 5 v-bind="$attrs"
6   - title="收入款单"
  6 + title="编辑"
7 7 width="30%"
8 8 :isDetail="true"
9 9 @ok="handleSubmit"
10 10 :showDetailBack="false"
11 11 okText="保存"
  12 + @visible-change="handleShow"
12 13 showFooter
13 14 :destroyOnClose="true"
14 15 >
15 16 <!-- <div>
16 17 <BasicForm @register="registerForm" />
17 18 </div> -->
18   - <div style="font-size: 15px">实际收款金额1$</div>
19   - <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10" auto-size />
20   - <div style="margin: 16px 0"></div>
21   - <div style="font-size: 15px">实际收款金额2$</div>
22   - <a-input v-model:value="input2" placeholder="请输入" :disabled="status === 10" auto-size />
  19 + <!-- <div style="font-size: 15px">研发复制费合计¥</div>
  20 + <a-input
  21 + v-model:value="input1"
  22 + placeholder="请输入"
  23 + :disabled="status1 === 'LOCKED'"
  24 + auto-size
  25 + /> -->
23 26 <div style="margin: 16px 0"></div>
24 27 <div style="font-size: 15px">项目开始时间</div>
25   - <a-date-picker v-model:value="input3" :disabled="status === 10" auto-size />
  28 + <a-date-picker v-model:value="input3" :disabled="status3 === 'LOCKED'" auto-size />
26 29 <div style="margin: 16px 0"></div>
27 30 <div style="font-size: 15px">项目结束时间</div>
28   - <a-date-picker v-model:value="input4" :disabled="status === 10" auto-size />
  31 + <a-date-picker v-model:value="input4" :disabled="status4 === 'LOCKED'" auto-size />
  32 + <!-- <div style="margin: 16px 0"></div>
  33 + <div style="font-size: 15px">西班牙已发提成¥</div>
  34 + <a-input
  35 + v-model:value="input2"
  36 + placeholder="请输入"
  37 + :disabled="status2 === 'LOCKED'"
  38 + auto-size
  39 + />
  40 + <div style="margin: 16px 0"></div>
  41 + <div style="font-size: 15px">中国团队已发提成¥</div>
  42 + <a-input
  43 + v-model:value="input5"
  44 + placeholder="请输入"
  45 + :disabled="status5 === 'LOCKED'"
  46 + auto-size
  47 + />
  48 + <div style="margin: 16px 0"></div>
  49 + <div style="font-size: 15px">实际汇率¥</div>
  50 + <a-input
  51 + v-model:value="input6"
  52 + placeholder="请输入"
  53 + :disabled="status6 === 'LOCKED'"
  54 + auto-size
  55 + /> -->
29 56 <div style="margin: 16px 0"></div>
30   -
31 57 <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
32 58 <template #appendFooter>
33 59 <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
... ... @@ -39,10 +65,11 @@
39 65 import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
40 66 import { BasicForm, FormSchema, useForm } from '@/components/Form';
41 67 import { defineComponent, ref, computed, unref, toRaw, reactive } from 'vue';
42   - import { getEmailList } from '/@/api/sys/config';
43   - import { updateAmount } from '@/api/project/invoice';
  68 + import { getServiceEdit } from '@/api/project/invoice';
44 69 import { useMessage } from '/@/hooks/web/useMessage';
45 70 import { ROLE } from './type.d';
  71 + import type { Dayjs } from 'dayjs';
  72 + import dayjs from 'dayjs';
46 73  
47 74 const emit = defineEmits(['success']);
48 75 const role = computed(() => {
... ... @@ -59,16 +86,16 @@
59 86 // label: '实际应收金额',
60 87 // },
61 88 {
62   - field: 'actualPayedAmount1',
  89 + field: 'developmentCopyRmbTotalPrice',
63 90 component: 'InputNumber',
64 91 labelWidth: 250,
65 92 colProps: {
66 93 span: 23,
67 94 },
68   - componentProps: () => ({
69   - disabled: status.value === 10,
70   - }),
71   - label: '实际应收金额1$',
  95 + // componentProps: () => ({
  96 + // disabled: status.value === 10,
  97 + // }),
  98 + label: '研发复制费合计¥',
72 99 },
73 100 {
74 101 field: 'actualPayedAmount2',
... ... @@ -77,9 +104,7 @@
77 104 colProps: {
78 105 span: 23,
79 106 },
80   - componentProps: () => ({
81   - disabled: status.value === 10,
82   - }),
  107 +
83 108 label: '实际应收金额2$',
84 109 },
85 110 {
... ... @@ -89,9 +114,7 @@
89 114 colProps: {
90 115 span: 23,
91 116 },
92   - componentProps: () => ({
93   - disabled: status.value === 10,
94   - }),
  117 +
95 118 label: '实际应收金额3$',
96 119 },
97 120 {
... ... @@ -101,9 +124,7 @@
101 124 colProps: {
102 125 span: 23,
103 126 },
104   - componentProps: () => ({
105   - disabled: status.value === 10,
106   - }),
  127 +
107 128 label: '其他费用金额$',
108 129 },
109 130 ];
... ... @@ -120,21 +141,60 @@
120 141 const { error } = createMessage;
121 142  
122 143 const update = ref();
123   - const status = ref();
  144 + const status1 = ref();
  145 + const status2 = ref();
  146 + const status3 = ref();
  147 + const status4 = ref();
  148 + const status5 = ref();
  149 + const status6 = ref();
124 150  
125 151 const input1 = ref();
126 152 const input2 = ref();
127 153 const input3 = ref();
128 154 const input4 = ref();
  155 +
  156 + const input5 = ref();
  157 + const input6 = ref();
129 158 const id = ref();
  159 + // function formatDate(dateStr: string): string {
  160 + // const date = new Date(dateStr);
  161 + // const year = date.getFullYear();
  162 + // const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,+1
  163 + // const day = String(date.getDate()).padStart(2, '0');
  164 + // const hours = String(date.getHours()).padStart(2, '0');
  165 + // const minutes = String(date.getMinutes()).padStart(2, '0');
  166 + // const seconds = String(date.getSeconds()).padStart(2, '0');
  167 +
  168 + // // 返回格式化后的字符串:'YYYY-MM-DD HH:mm:ss'
  169 + // return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  170 + // }
  171 + // function formatDateToDateOnly(dateStr: string): string {
  172 + // const date = new Date(dateStr);
  173 + // const year = date.getFullYear();
  174 + // const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,+1
  175 + // const day = String(date.getDate()).padStart(2, '0');
  176 +
  177 + // // 返回格式化后的日期字符串:'YYYY-MM-DD'
  178 + // return `${year}-${month}-${day}`;
  179 + // }
130 180 const [register, { setDrawerProps, closeDrawer }] = useDrawerInner((data) => {
131 181 // 方式1
132   - status.value = data.data.invoiceStatus;
133   - id.value = data.data.invoiceId;
134   - input1.value = data.data.invoiceActualPayedAmount1;
135   - input2.value = data.data.invoiceActualPayedAmount2;
136   - input3.value = data.data.invoiceActualPayedAmount3;
137   - input4.value = data.data.invoiceOtherAmount;
  182 + if (data.data.lockFields) {
  183 + status1.value = data?.data?.lockFields?.developmentCopyRmbTotalPrice;
  184 + status2.value = data?.data?.lockFields?.spainPaidRmbCommission;
  185 + status3.value = data?.data?.lockFields?.projectInnerProfitInfoStartTime;
  186 + status4.value = data?.data?.lockFields?.projectInnerProfitInfoEndTime;
  187 + status5.value = data?.data?.lockFields?.paidRmbCommission;
  188 + status6.value = data?.data?.lockFields?.actualExchangeRate;
  189 + }
  190 + id.value = data?.data?.projectNoPrefix;
  191 + input1.value = data?.data?.developmentCopyRmbTotalPrice?.toFixed(2);
  192 + input2.value = data?.data?.spainPaidRmbCommission?.toFixed(2);
  193 + input3.value = data?.data?.produceStartTime ? dayjs(data?.data?.produceStartTime) : null;
  194 + input4.value = data?.data?.produceEndTime ? dayjs(data?.data?.produceEndTime) : null;
  195 + input5.value = data?.data?.paidRmbCommission?.toFixed(2);
  196 + input6.value = data?.data?.actualExchangeRate?.toFixed(2);
  197 +
138 198 resetFields();
139 199 setDrawerProps({ confirmLoading: false });
140 200 setFieldsValue({
... ... @@ -150,18 +210,26 @@
150 210 // id: update.value.data.id,
151 211 // bgUrl: update.value.data.bgUrl,
152 212 // };
153   - if (!input1.value || !input2.value || !input3.value || !input4.value) {
  213 + if (!input3.value || !input4.value) {
154 214 error('选项不能为空');
155 215 } else {
156   - await updateAmount({
157   - id: id.value,
158   - actualPayedAmount1: input1.value,
159   - actualPayedAmount2: input2.value,
160   - actualPayedAmount3: input3.value,
161   - otherAmount: input4.value,
  216 + await getServiceEdit({
  217 + projectNoPrefix: id.value,
  218 + projectInnerProfitInfoStartTime: input3.value,
  219 + projectInnerProfitInfoEndTime: input4.value,
162 220 });
163 221 emit('success');
164 222 closeDrawer();
165 223 }
166 224 }
  225 + function handleShow(visible: boolean) {
  226 + if (!visible) {
  227 + input1.value = '';
  228 + input2.value = '';
  229 + input3.value = '';
  230 + input4.value = '';
  231 + input5.value = '';
  232 + input6.value = '';
  233 + }
  234 + }
167 235 </script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/HistoryDetail.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + @register="register"
  4 + v-bind="$attrs"
  5 + title="操作记录"
  6 + width="60%"
  7 + :isDetail="true"
  8 + :showDetailBack="false"
  9 + okText="保存"
  10 + :destroyOnClose="true"
  11 + >
  12 + <Tabs v-model:activeKey="activeKey" className="my-0">
  13 + <TabPanel :key="1" tab="操作记录" className="w-full">
  14 + <a-list :pagination="pagination1" className="w-full">
  15 + <template v-for="item in list1" :key="item.id">
  16 + <a-list-item class="list">
  17 + <a-list-item-meta>
  18 + <template #avatar> </template>
  19 + <template #title>
  20 + <span>{{ item.userName }}</span>
  21 + </template>
  22 + <template #description>
  23 + <div class="description">
  24 + {{ item.optType }}
  25 + </div>
  26 + <div class="info">
  27 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  28 + </div>
  29 + </template>
  30 + </a-list-item-meta>
  31 + </a-list-item>
  32 + </template>
  33 + </a-list>
  34 + </TabPanel>
  35 + <TabPanel :key="2" tab="审批记录" className="w-full">
  36 + <a-list :pagination="pagination2" className="w-full">
  37 + <template v-for="item in list2" :key="item.id">
  38 + <a-list-item class="list">
  39 + <a-list-item-meta>
  40 + <template #avatar> </template>
  41 + <template #title>
  42 + <span>{{ item.userName }}</span>
  43 + </template>
  44 + <template #description>
  45 + <div class="description">
  46 + {{ item.optType }}
  47 + </div>
  48 + <div class="info">
  49 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  50 + </div>
  51 + </template>
  52 + </a-list-item-meta>
  53 + </a-list-item>
  54 + </template>
  55 + </a-list>
  56 + </TabPanel>
  57 + </Tabs>
  58 +
  59 + <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
  60 + <template #appendFooter>
  61 + <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
  62 + </template>
  63 + </BasicDrawer>
  64 +</template>
  65 +<script lang="ts">
  66 + import { defineComponent, ref, computed } from 'vue';
  67 + import { Tabs, List } from 'ant-design-vue';
  68 + import { FormSchema } from '/@/components/Form/index';
  69 +
  70 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  71 + import { getProjectOptLog } from '/@/api/project/invoice';
  72 + import { formatToDateTime } from '/@/utils/dateUtil';
  73 +
  74 + const TabPanel = Tabs.TabPane;
  75 +
  76 + const schemas: FormSchema[] = [
  77 + {
  78 + field: '订单号',
  79 + component: 'Input',
  80 + label: '字段1',
  81 + componentProps: {
  82 + readonly: true,
  83 + disabled: true,
  84 + },
  85 + colProps: {
  86 + span: 12,
  87 + },
  88 + defaultValue: '111',
  89 + },
  90 + {
  91 + field: 'field2',
  92 + component: 'Input',
  93 + label: '字段2',
  94 + colProps: {
  95 + span: 12,
  96 + },
  97 + },
  98 + ];
  99 + const achieveList = [
  100 + {
  101 + key: '1',
  102 + name: '操作记录',
  103 + },
  104 + {
  105 + key: '2',
  106 + name: '审批记录',
  107 + },
  108 + ];
  109 + export default defineComponent({
  110 + components: {
  111 + BasicDrawer,
  112 + Tabs,
  113 + TabPanel,
  114 + [List.name]: List,
  115 + [List.Item.name]: List.Item,
  116 + AListItemMeta: List.Item.Meta,
  117 + },
  118 + props: {
  119 + onGoCheckDetail: {
  120 + type: Function,
  121 + },
  122 + },
  123 + setup() {
  124 + const list1 = ref([]);
  125 + const total1 = ref(0);
  126 + const page1 = ref(1);
  127 +
  128 + const list2 = ref([]);
  129 + const total2 = ref(0);
  130 + const page2 = ref(1);
  131 + const orderId = ref('');
  132 + const activeKey = ref(1);
  133 +
  134 + const getOrderOptLogFunc = async (data, index, page) => {
  135 + console.log('%c [ data ]-135', 'font-size:13px; background:pink; color:#bf2c9f;', data);
  136 + if (index === 1) {
  137 + const res = await getProjectOptLog({
  138 + projectNoPrefix: data,
  139 + type: [30],
  140 + page: page,
  141 + pageSize: 20
  142 + });
  143 + list1.value = res.items;
  144 + total1.value = res.total;
  145 + page1.value = page;
  146 + } else {
  147 + const res = await getProjectOptLog({
  148 + projectNoPrefix: data,
  149 + type: [1,3],
  150 + page: page,
  151 + pageSize: 20
  152 + });
  153 + list2.value = res.items;
  154 + total2.value = res.total;
  155 + page2.value = page;
  156 + }
  157 + };
  158 + const [register] = useDrawerInner((data) => {
  159 + orderId.value = data.data.projectNoPrefix;
  160 + getOrderOptLogFunc(orderId.value, 1, 1);
  161 + getOrderOptLogFunc(orderId.value, 2, 1);
  162 + });
  163 +
  164 + const pagination1 = computed(() => {
  165 + return {
  166 + show: true,
  167 + pageSize: 20,
  168 + page: page1.value,
  169 + total: total1.value,
  170 + onChange(cur) {
  171 + getOrderOptLogFunc(orderId.value, 1, cur);
  172 + },
  173 + };
  174 + });
  175 +
  176 + const pagination2 = computed(() => {
  177 + return {
  178 + show: true,
  179 + pageSize: 20,
  180 + page: page1.value,
  181 + total: total1.value,
  182 + onChange(cur) {
  183 + getOrderOptLogFunc(orderId.value, 2, cur);
  184 + },
  185 + };
  186 + });
  187 +
  188 + return {
  189 + register,
  190 + schemas,
  191 + achieveList,
  192 + list1,
  193 + list2,
  194 + prefixCls: 'account-center',
  195 + pagination1,
  196 + pagination2,
  197 + activeKey,
  198 + formatToDateTime,
  199 + };
  200 + },
  201 + });
  202 +</script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/data.tsx
... ... @@ -113,14 +113,14 @@ export const COLUMNS = [
113 113 dataIndex: 'orderCount',
114 114 width: 100,
115 115 },
116   - {
117   - title: '生产科单价¥',
118   - dataIndex: 'productionDepartmentPrice',
119   - width: 120,
120   - customRender: (column) => {
121   - return column.record?.productionDepartmentPrice?.toFixed(2);
122   - },
123   - },
  116 + // {
  117 + // title: '生产科单价¥',
  118 + // dataIndex: 'productionDepartmentPrice',
  119 + // width: 120,
  120 + // customRender: (column) => {
  121 + // return column.record?.productionDepartmentPrice?.toFixed(2);
  122 + // },
  123 + // },
124 124 {
125 125 title: '生产科总价¥',
126 126 dataIndex: 'productionDepartmentTotalPrice',
... ... @@ -151,7 +151,7 @@ export const COLUMNS = [
151 151 width: 120,
152 152 customRender: (column) => {
153 153 if (column.record?.predictRatio) {
154   - return column.record?.predictRatio?.toFixed(2) + '%';
  154 + return (column.record?.predictRatio * 100).toFixed(2) + '%';
155 155 }
156 156 return column.record?.predictRatio?.toFixed(2);
157 157 },
... ... @@ -162,7 +162,7 @@ export const COLUMNS = [
162 162 width: 190,
163 163 customRender: (column) => {
164 164 if (column.record?.predictAndActualRatio) {
165   - return column.record?.predictAndActualRatio?.toFixed(2) + '%';
  165 + return (column.record?.predictAndActualRatio * 100).toFixed(2) + '%';
166 166 }
167 167 return column.record?.predictAndActualRatio?.toFixed(2);
168 168 },
... ... @@ -181,7 +181,7 @@ export const COLUMNS = [
181 181 width: 120,
182 182 customRender: (column) => {
183 183 if (column.record?.beforeGrossProfitRate) {
184   - return column.record?.beforeGrossProfitRate?.toFixed(2) + '%';
  184 + return (column.record?.beforeGrossProfitRate * 100).toFixed(2) + '%';
185 185 }
186 186 return column.record?.beforeGrossProfitRate?.toFixed(2);
187 187 },
... ... @@ -200,7 +200,7 @@ export const COLUMNS = [
200 200 width: 120,
201 201 customRender: (column) => {
202 202 if (column.record?.grossProfitRate) {
203   - return column.record?.grossProfitRate?.toFixed(2) + '%';
  203 + return (column.record?.grossProfitRate * 100).toFixed(2) + '%';
204 204 }
205 205 return column.record?.grossProfitRate?.toFixed(2);
206 206 },
... ... @@ -235,12 +235,27 @@ export const COLUMNS = [
235 235 width: 150,
236 236 customRender: (column) => {
237 237 if (column.record?.innerProductionProfitRate) {
238   - return column.record?.innerProductionProfitRate?.toFixed(2) + '%';
  238 + return (column.record?.innerProductionProfitRate * 100).toFixed(2) + '%';
239 239 }
240 240 return column.record?.innerProductionProfitRate?.toFixed(2);
241 241 },
242 242 },
243 243 {
  244 + title: '状态',
  245 + dataIndex: 'status',
  246 + width: 120,
  247 + customRender: (column) => {
  248 + if (column.record?.innerProductionStatus === null ||
  249 + column.record?.innerProductionStatus === -1) {
  250 + return '未完成';
  251 + } else if (column.record?.innerProductionStatus === 0) {
  252 + return '待审核';
  253 + } else if (column.record?.innerProductionStatus === 1) {
  254 + return '已审核';
  255 + }
  256 + },
  257 + },
  258 + {
244 259 title: '文件',
245 260 dataIndex: 'fileUrl',
246 261 width: 120,
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/index.vue
1 1 <template>
2 2 <div>
3 3 <BasicTable @register="registerTable" :bordered="true">
  4 + <template #headerTop>
  5 + <a-alert type="info" show-icon>
  6 + <template #message>
  7 + <template v-if="checkedKeys.length > 0">
  8 + <span>已选中{{ checkedKeys.length }}条记录(可跨页)</span>
  9 + <a-button
  10 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  11 + type="link"
  12 + @click="handleClearChoose"
  13 + size="small"
  14 + >清空</a-button
  15 + >
  16 + </template>
  17 + <template v-else>
  18 + <span>未选中任何订单</span>
  19 + </template>
  20 + </template>
  21 + </a-alert>
  22 + </template>
4 23 <template #bodyCell="{ column, record }">
5 24 <template v-if="column.key === 'picUrl'">
6 25 <img :z-index="100000" :width="50" :height="50" :src="record?.picUrl" />
7 26 </template>
8   - <!-- <template v-if="column.key === 'action'">
9   - <TableAction :actions="createActions(record)" />
10   - </template> -->
  27 + <template v-if="column.key === 'action'">
  28 + <TableAction
  29 + :actions="createActions(record)"
  30 + :dropDownActions="createDropActions(record)"
  31 + />
  32 + </template>
11 33 <!-- <template v-if="column.key === 'relationValue'">
12 34 <a-input
13 35 v-if="record.settingValue === 'A01'"
... ... @@ -19,6 +41,15 @@
19 41 </span>
20 42 </template> -->
21 43 </template>
  44 + <template #toolbar>
  45 + <a-button
  46 + type="primary"
  47 + @click="handleExport"
  48 + v-if="role == ROLE.ADMIN || role == ROLE.FINANCE"
  49 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  50 + >导出</a-button
  51 + >
  52 + </template>
22 53 </BasicTable>
23 54 <!-- <BasicModal
24 55 title="拒绝原因"
... ... @@ -32,25 +63,44 @@
32 63 <a-textarea :rows="6" placeholder="请输入拒绝原因" v-model:value="message" />
33 64 </div>
34 65 </BasicModal> -->
  66 + <CheckDetail @register="checkModalRegister" :onGoFormDetail="handleGoFormDetail" />
35 67 <FinanceEdit @register="registerFinanceEdit" @success="handleSuccess" />
  68 + <HistoryDetail @register="registerHistoryDetail" />
36 69 </div>
37 70 </template>
38 71 <script setup lang="ts">
39 72 import { BasicTable, useTable, TableAction } from '/@/components/Table';
40   - import { getInnerProduceProfit } from '@/api/project/invoice';
  73 + import { getInnerProduceProfit, setInnerProfitSetStatus } from '@/api/project/invoice';
41 74 import { searchFormSchema, COLUMNS } from './data';
42 75 import { BasicModal, useModal } from '/@/components/Modal';
43 76 import { useMessage } from '/@/hooks/web/useMessage';
44   - import { onMounted, ref } from 'vue';
  77 + import { onMounted, ref, computed } from 'vue';
  78 + import axios from 'axios';
45 79 import { useDrawer } from '/@/components/Drawer';
46 80 import FinanceEdit from './FinanceEdit.vue';
  81 + import CheckDetail from './CheckDetail.vue';
  82 + import HistoryDetail from './HistoryDetail.vue';
47 83 import { useOrderStoreWithOut } from '/@/store/modules/order';
  84 + import { useUserStoreWithOut } from '/@/store/modules/user';
  85 + import { RoleEnum } from '/@/enums/roleEnum';
  86 + import { ROLE } from '../../../type.d';
48 87  
49 88 const { createMessage } = useMessage();
50 89 const { error } = createMessage;
51 90 const message = ref();
  91 + const checkedKeys = ref<string[]>([]);
  92 + const invoiceIdKeys = ref<string[]>([]);
  93 + const checkIdKeys = ref<string[]>([]);
  94 + const detailProjectNoKeys = ref<string[]>([]);
52 95 const orderStore = useOrderStoreWithOut();
  96 + const [checkModalRegister, { openDrawer: openCheckDetailDrawer }] = useDrawer();
53 97 const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer();
  98 + const [registerHistoryDetail, { openDrawer: openHistoryDetail }] = useDrawer();
  99 + const userStore = useUserStoreWithOut();
  100 + const user = userStore.getUserInfo;
  101 + const role = computed(() => {
  102 + return user?.roleSmallVO?.code;
  103 + });
54 104 const handleOk = (record) => {
55 105 // 修改父组件的状态
56 106 closeModal();
... ... @@ -60,12 +110,18 @@
60 110 message.value = '';
61 111 }
62 112 };
63   - const [registerTable, { reload }] = useTable({
  113 + const [registerTable, { reload, setSelectedRowKeys }] = useTable({
64 114 api: getInnerProduceProfit,
65 115 bordered: true,
66 116 columns: COLUMNS,
67 117 clickToRowSelect: false,
68   - rowKey: 'id',
  118 + rowKey: (record) => record.projectNoPrefix || record.id || record.serialNumber,
  119 + rowSelection: {
  120 + type: 'checkbox',
  121 + selectedRowKeys: checkedKeys as any,
  122 + onSelect: onSelect,
  123 + onSelectAll: onSelectAll,
  124 + },
69 125 formConfig: {
70 126 labelWidth: 120,
71 127 schemas: searchFormSchema,
... ... @@ -77,50 +133,79 @@
77 133 tableSetting: {
78 134 setting: false,
79 135 },
  136 + actionColumn: {
  137 + width: 260,
  138 + title: 'Action',
  139 + dataIndex: 'action',
  140 + },
80 141 });
81 142  
82   - // function createActions(record: any): any[] {
83   - // if (!record.editable) {
84   - // return [
85   - // // {
86   - // // label: '财务编辑',
87   - // // onClick: handleFinanceEdit.bind(null, record),
88   - // // },
89   - // // {
90   - // // label: '编辑',
91   - // // onClick: handleEdit.bind(null, record),
92   - // // },
93   - // // {
94   - // // label: '删除',
95   - // // popConfirm: {
96   - // // title: '确认删除?',
97   - // // confirm: handleDelete.bind(null, record),
98   - // // },
99   - // // },
100   - // // {
101   - // // label: '申请权限',
102   - // // // popConfirm: {
103   - // // // title: '确认申请?',
104   - // // // confirm: handleFalse.bind(null, record),
105   - // // // },
106   - // // onClick: handleFalse.bind(null, record),
107   - // // },
108   - // ];
109   - // }
110   - // return [
111   - // {
112   - // label: '保存',
113   - // onClick: handleSave.bind(null, record),
114   - // },
115   - // {
116   - // label: '取消',
117   - // popConfirm: {
118   - // title: '是否取消编辑',
119   - // confirm: handleCancel.bind(null, record),
120   - // },
121   - // },
122   - // ];
123   - // }
  143 + function createActions(record: any): any[] {
  144 + if (!record.editable) {
  145 + return [
  146 + {
  147 + label: '财务编辑',
  148 + onClick: handleFinanceEdit.bind(null, record),
  149 + },
  150 + // {
  151 + // label: '编辑',
  152 + // onClick: handleEdit.bind(null, record),
  153 + // },
  154 + // {
  155 + // label: '删除',
  156 + // popConfirm: {
  157 + // title: '确认删除?',
  158 + // confirm: handleDelete.bind(null, record),
  159 + // },
  160 + // },
  161 + {
  162 + label: '申请权限',
  163 + // popConfirm: {
  164 + // title: '确认申请?',
  165 + // confirm: handleFalse.bind(null, record),
  166 + // },
  167 + onClick: handleFalse.bind(null, record),
  168 + },
  169 + {
  170 + label: '审核通过',
  171 + popConfirm: {
  172 + title: '确认审核?',
  173 + confirm: () => {
  174 + if (record.innerProductionStatus === 0 && role.value === ROLE.ADMIN) {
  175 + handleStatus(record, true);
  176 + }
  177 + return;
  178 + },
  179 + },
  180 + },
  181 + ];
  182 + }
  183 + return [
  184 + {
  185 + label: '保存',
  186 + onClick: handleSave.bind(null, record),
  187 + },
  188 + {
  189 + label: '取消',
  190 + popConfirm: {
  191 + title: '是否取消编辑',
  192 + confirm: handleCancel.bind(null, record),
  193 + },
  194 + },
  195 + ];
  196 + }
  197 +
  198 + function createDropActions(record: any) {
  199 + if (!record.editable) {
  200 + const actions = [
  201 + {
  202 + label: '历史记录',
  203 + onClick: handleHistoryDetail.bind(null, record),
  204 + },
  205 + ];
  206 + return actions;
  207 + }
  208 + }
124 209  
125 210 onMounted(async () => {
126 211 await orderStore.getDict();
... ... @@ -132,10 +217,11 @@
132 217 });
133 218 }
134 219  
135   - // async function handleFalse(record: any) {
136   - // console.log(record);
137   - // // openModal(true, { record });
138   - // }
  220 + function handleFalse(record, e) {
  221 + openCheckDetailDrawer(true, record);
  222 + e?.stopPropagation();
  223 + return false;
  224 + }
139 225  
140 226 async function handleSave(record) {
141 227 await saveConfig({ id: record.id, relationValue: record.relationValue });
... ... @@ -154,8 +240,170 @@
154 240 record.onEdit?.(false, false);
155 241 }
156 242  
157   - async function handleDelete(record) {
158   - await deleteConfig({ ids: [record.id] });
  243 + // async function handleDelete(record) {
  244 + // await deleteConfig({ ids: [record.id] });
  245 + // reload();
  246 + // }
  247 + function handleSuccess() {
  248 + setTimeout(() => {
  249 + reload();
  250 + }, 50);
  251 + }
  252 +
  253 + async function handleStatus(record, status) {
  254 + try {
  255 + await setInnerProfitSetStatus({
  256 + projectNo: record.projectNoPrefix,
  257 + customerCode: record.customerCode
  258 + });
  259 + reload();
  260 + } catch (error) {
  261 + console.error(error);
  262 + }
  263 + }
  264 +
  265 + function handleHistoryDetail(record) {
  266 + openHistoryDetail(true, {
  267 + data: record,
  268 + });
  269 + }
  270 +
  271 + function handleGoFormDetail() {
  272 + // Add your logic for form detail navigation
  273 + }
  274 +
  275 + function handleClearChoose() {
  276 + checkedKeys.value = [];
  277 + detailProjectNoKeys.value = [];
  278 + invoiceIdKeys.value = [];
  279 + checkIdKeys.value = [];
  280 + }
  281 + async function onSelect(record, selected: boolean) {
  282 + const rowKey = record.projectNoPrefix || record.id || record.serialNumber;
  283 + if (selected) {
  284 + checkedKeys.value = [...checkedKeys.value, rowKey];
  285 +
  286 + // 使用projectNoPrefix作为单个值,添加到detailProjectNoKeys中
  287 + if (record.projectNoPrefix !== undefined) {
  288 + // 如果是单个值,检查是否已存在,然后添加到数组
  289 + if (!detailProjectNoKeys.value.includes(record.projectNoPrefix)) {
  290 + detailProjectNoKeys.value.push(record.projectNoPrefix);
  291 + }
  292 + }
  293 +
  294 + if (record.invoiceId !== undefined) {
  295 + invoiceIdKeys.value = [...invoiceIdKeys.value, record.invoiceId];
  296 + }
  297 +
  298 + if (record.checkId !== undefined) {
  299 + checkIdKeys.value = [...checkIdKeys.value, record.checkId];
  300 + }
  301 + } else {
  302 + checkedKeys.value = checkedKeys.value.filter((key) => key !== rowKey);
  303 +
  304 + // 从detailProjectNoKeys中移除projectNoPrefix
  305 + if (record.projectNoPrefix !== undefined) {
  306 + // 直接移除单个值
  307 + detailProjectNoKeys.value = detailProjectNoKeys.value.filter(
  308 + (projectNo) => projectNo !== record.projectNoPrefix
  309 + );
  310 + }
  311 +
  312 + if (record.invoiceId !== undefined) {
  313 + invoiceIdKeys.value = invoiceIdKeys.value.filter(
  314 + (invoiceId) => invoiceId !== record.invoiceId,
  315 + );
  316 + }
  317 +
  318 + if (record.checkId !== undefined) {
  319 + checkIdKeys.value = checkIdKeys.value.filter((checkId) => checkId !== record.checkId);
  320 + }
  321 + }
  322 + setSelectedRowKeys(checkedKeys.value as any);
  323 + }
  324 +
  325 + function onSelectAll(selected: boolean, selectedRows: any[]) {
  326 + if (selected) {
  327 + // 先清空之前的选择,避免重复添加
  328 + checkedKeys.value = [];
  329 + detailProjectNoKeys.value = [];
  330 + invoiceIdKeys.value = [];
  331 + checkIdKeys.value = [];
  332 +
  333 + // 重新添加所有选中的行
  334 + selectedRows.forEach((row) => {
  335 + const rowKey = row.projectNoPrefix || row.id || row.serialNumber;
  336 + checkedKeys.value.push(rowKey);
  337 +
  338 + // 使用projectNoPrefix作为单个值,添加到detailProjectNoKeys中
  339 + if (row.projectNoPrefix !== undefined) {
  340 + // 由于已经清空了数组,不需要检查是否存在
  341 + detailProjectNoKeys.value.push(row.projectNoPrefix);
  342 + }
  343 +
  344 + if (row.invoiceId !== undefined) {
  345 + invoiceIdKeys.value.push(row.invoiceId);
  346 + }
  347 +
  348 + if (row.checkId !== undefined) {
  349 + checkIdKeys.value.push(row.checkId);
  350 + }
  351 + });
  352 + } else {
  353 + // 取消全选时,清空所有选择
  354 + checkedKeys.value = [];
  355 + detailProjectNoKeys.value = [];
  356 + invoiceIdKeys.value = [];
  357 + checkIdKeys.value = [];
  358 + }
  359 + setSelectedRowKeys(checkedKeys.value as any);
  360 + }
  361 + function handleExport() {
  362 + if (checkedKeys.value.length <= 0) {
  363 + createMessage.warn('请选择数据!');
  364 + return;
  365 + }
  366 + const token = userStore.getToken;
  367 + axios
  368 + .post(
  369 + '/basic-api/project/innerProfitInfo/exportExcel',
  370 + { detailProjectNo: detailProjectNoKeys.value },
  371 + {
  372 + headers: {
  373 + Authorization: `${token}`, // 去掉引号
  374 + },
  375 + responseType: 'blob', // 设置响应类型为 'blob'
  376 + },
  377 + )
  378 + .then((response) => {
  379 + // 创建一个 Blob 对象来保存二进制数据
  380 + const blob = new Blob([response.data], { type: 'application/zip' });
  381 + const getFormattedDate = (): string => {
  382 + const date = new Date();
  383 +
  384 + const year = date.getFullYear();
  385 + const month = String(date.getMonth() + 1).padStart(2, '0');
  386 + const day = String(date.getDate()).padStart(2, '0');
  387 +
  388 + const hours = String(date.getHours()).padStart(2, '0');
  389 + const minutes = String(date.getMinutes()).padStart(2, '0');
  390 + const seconds = String(date.getSeconds()).padStart(2, '0');
  391 +
  392 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  393 + };
  394 + const date = getFormattedDate();
  395 + // 创建一个链接元素用于下载
  396 + const link = document.createElement('a');
  397 + link.href = window.URL.createObjectURL(blob);
  398 + link.download = `内产生产净利润分析表${date}.xlsx`; // 你可以为文件命名
  399 + document.body.appendChild(link);
  400 + link.click(); // 自动点击链接,触发下载
  401 + document.body.removeChild(link); // 下载完成后移除链接
  402 + })
  403 + .catch((error) => {
  404 + console.error(error);
  405 + });
  406 + handleClearChoose();
159 407 reload();
160 408 }
161 409 </script>
... ...
src/views/project/finance/financeProfit/ProductProfit/InnerProduce/tableData.tsx
... ... @@ -21,74 +21,18 @@ const orderStore = useOrderStoreWithOut();
21 21 // 基本信息
22 22 export const FIELDS_BASE_INFO = [
23 23 {
24   - field: 'customerCode',
  24 + field: 'projectInnerProfitInfoStartTime',
25 25 component: 'Select',
26 26 labelWidth: 150,
27   - label: '客户编码',
  27 + label: '项目开始时间',
28 28 rules: [{ required: true }],
29 29 },
30 30 {
31   - field: 'projectNo',
32   - component: 'Input',
33   - labelWidth: 150,
34   - label: '项目号',
35   - rules: [{ required: true }],
36   - },
37   - {
38   - field: 'productionDepartment',
  31 + field: 'projectInnerProfitInfoEndTime',
39 32 component: 'Select',
40   - // componentProps: {
41   - // options: productionDepartmentOptions,
42   - // },
43 33 labelWidth: 150,
44   - label: '生产科',
  34 + label: '项目结束时间',
45 35 rules: [{ required: true }],
46 36 },
47   -
48   - {
49   - field: 'innerNo',
50   - component: 'Input',
51   - labelWidth: 150,
52   - label: '内部编号',
53   - rules: [
54   - { required: true },
55   - {
56   - validator: async (rule, value) => {
57   - if (value.includes(' ')) {
58   - return Promise.reject();
59   - }
60   - return Promise.resolve();
61   - },
62   - message: '内容存在空格,请检查',
63   - trigger: ['change', 'blur'],
64   - },
65   - ],
66   - },
67   - {
68   - field: 'customerPo',
69   - component: 'Input',
70   - labelWidth: 150,
71   - label: '客户po号',
72   - rules: [{ required: true }],
73   - },
74   - {
75   - field: 'customerStyle',
76   - component: 'Input',
77   - labelWidth: 150,
78   - label: '客户STYLE',
79   - rules: [
80   - { required: true },
81   - {
82   - validator: async (rule, value) => {
83   - if (value.includes(' ')) {
84   - return Promise.reject();
85   - }
86   - return Promise.resolve();
87   - },
88   - message: '内容存在空格,请检查',
89   - trigger: ['change', 'blur'],
90   - },
91   - ],
92   - },
93 37 ];
94 38  
... ...
src/views/project/finance/financeProfit/ServiceProfit/PackageProfit/ApproveReason.vue
... ... @@ -32,7 +32,7 @@
32 32 orderId: baseFieldValues.value.orderId,
33 33 packetActualRmbTotalPrice: baseFieldValues.value.packetActualRmbTotalPrice,
34 34 applyRemark: baseFieldValues.value.applyRemark,
35   - type: 60,
  35 + type: 80,
36 36 });
37 37 emit('success');
38 38 setTimeout(() => {
... ...
src/views/project/finance/financeProfit/ServiceProfit/PackageProfit/FinanceEdit.vue
... ... @@ -122,7 +122,7 @@
122 122 const [register, { setDrawerProps, closeDrawer }] = useDrawerInner((data) => {
123 123 // 方式1
124 124 if (data.data.lockFields) {
125   - status.value = data.data.lockFields?.packetActualRmbPrice;
  125 + status.value = data.data.lockFields?.packetActualRmbTotalPrice;
126 126 }
127 127 id.value = data.data.orderId;
128 128 input1.value = data.data?.packetActualRmbTotalPrice;
... ...
src/views/project/finance/financeProfit/ServiceProfit/PackageProfit/HistoryDetail.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + @register="register"
  4 + v-bind="$attrs"
  5 + title="操作记录"
  6 + width="60%"
  7 + :isDetail="true"
  8 + :showDetailBack="false"
  9 + okText="保存"
  10 + :destroyOnClose="true"
  11 + >
  12 + <Tabs v-model:activeKey="activeKey" className="my-0">
  13 + <TabPanel :key="1" tab="操作记录" className="w-full">
  14 + <a-list :pagination="pagination1" className="w-full">
  15 + <template v-for="item in list1" :key="item.id">
  16 + <a-list-item class="list">
  17 + <a-list-item-meta>
  18 + <template #avatar> </template>
  19 + <template #title>
  20 + <span>{{ item.userName }}</span>
  21 + </template>
  22 + <template #description>
  23 + <div class="description">
  24 + {{ item.optType }}
  25 + </div>
  26 + <div class="info">
  27 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  28 + </div>
  29 + </template>
  30 + </a-list-item-meta>
  31 + </a-list-item>
  32 + </template>
  33 + </a-list>
  34 + </TabPanel>
  35 + <TabPanel :key="2" tab="审批记录" className="w-full">
  36 + <a-list :pagination="pagination2" className="w-full">
  37 + <template v-for="item in list2" :key="item.id">
  38 + <a-list-item class="list">
  39 + <a-list-item-meta>
  40 + <template #avatar> </template>
  41 + <template #title>
  42 + <span>{{ item.userName }}</span>
  43 + </template>
  44 + <template #description>
  45 + <div class="description">
  46 + {{ item.optType }}
  47 + </div>
  48 + <div class="info">
  49 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  50 + </div>
  51 + </template>
  52 + </a-list-item-meta>
  53 + </a-list-item>
  54 + </template>
  55 + </a-list>
  56 + </TabPanel>
  57 + </Tabs>
  58 +
  59 + <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
  60 + <template #appendFooter>
  61 + <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
  62 + </template>
  63 + </BasicDrawer>
  64 +</template>
  65 +<script lang="ts">
  66 + import { defineComponent, ref, computed } from 'vue';
  67 + import { Tabs, Progress, Row, Col, List } from 'ant-design-vue';
  68 + import { FormSchema, useForm } from '/@/components/Form/index';
  69 +
  70 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  71 + import { getOrderCostDetailedOptLog } from '/@/api/project/invoice';
  72 + import { formatToDateTime } from '/@/utils/dateUtil';
  73 +
  74 + const TabPanel = Tabs.TabPane;
  75 +
  76 + const schemas: FormSchema[] = [
  77 + {
  78 + field: '订单号',
  79 + component: 'Input',
  80 + label: '字段1',
  81 + componentProps: {
  82 + readonly: true,
  83 + disabled: true,
  84 + },
  85 + colProps: {
  86 + span: 12,
  87 + },
  88 + defaultValue: '111',
  89 + },
  90 + {
  91 + field: 'field2',
  92 + component: 'Input',
  93 + label: '字段2',
  94 + colProps: {
  95 + span: 12,
  96 + },
  97 + },
  98 + ];
  99 + const achieveList = [
  100 + {
  101 + key: '1',
  102 + name: '操作记录',
  103 + },
  104 + {
  105 + key: '2',
  106 + name: '审批记录',
  107 + },
  108 + ];
  109 + export default defineComponent({
  110 + components: {
  111 + BasicDrawer,
  112 + Tabs,
  113 + TabPanel,
  114 + [List.name]: List,
  115 + [List.Item.name]: List.Item,
  116 + AListItemMeta: List.Item.Meta,
  117 + },
  118 + props: {
  119 + onGoCheckDetail: {
  120 + type: Function,
  121 + },
  122 + },
  123 + setup() {
  124 + const list1 = ref([]);
  125 + const total1 = ref(0);
  126 + const page1 = ref(1);
  127 +
  128 + const list2 = ref([]);
  129 + const total2 = ref(0);
  130 + const page2 = ref(1);
  131 + const orderId = ref('');
  132 + const activeKey = ref(1);
  133 +
  134 + const getOrderOptLogFunc = async (data, index, page) => {
  135 + console.log('%c [ data ]-135', 'font-size:13px; background:pink; color:#bf2c9f;', data);
  136 + if (index === 1) {
  137 + const res = await getOrderCostDetailedOptLog({
  138 + orderId: data,
  139 + type: [20],
  140 + page: page,
  141 + pageSize: 20
  142 + });
  143 + list1.value = res.items;
  144 + total1.value = res.total;
  145 + page1.value = page;
  146 + } else {
  147 + const res = await getOrderCostDetailedOptLog({
  148 + orderId: data,
  149 + type: [80,81],
  150 + page: page,
  151 + pageSize: 20
  152 + });
  153 + list2.value = res.items;
  154 + total2.value = res.total;
  155 + page2.value = page;
  156 + }
  157 + };
  158 + const [register] = useDrawerInner((data) => {
  159 + orderId.value = data.data.orderId;
  160 + getOrderOptLogFunc(orderId.value, 1, 1);
  161 + getOrderOptLogFunc(orderId.value, 2, 1);
  162 + });
  163 +
  164 + const pagination1 = computed(() => {
  165 + return {
  166 + show: true,
  167 + pageSize: 20,
  168 + page: page1.value,
  169 + total: total1.value,
  170 + onChange(cur) {
  171 + getOrderOptLogFunc(orderId.value, 1, cur);
  172 + },
  173 + };
  174 + });
  175 +
  176 + const pagination2 = computed(() => {
  177 + return {
  178 + show: true,
  179 + pageSize: 20,
  180 + page: page1.value,
  181 + total: total1.value,
  182 + onChange(cur) {
  183 + getOrderOptLogFunc(orderId.value, 2, cur);
  184 + },
  185 + };
  186 + });
  187 +
  188 + return {
  189 + register,
  190 + schemas,
  191 + achieveList,
  192 + list1,
  193 + list2,
  194 + prefixCls: 'account-center',
  195 + pagination1,
  196 + pagination2,
  197 + activeKey,
  198 + formatToDateTime,
  199 + };
  200 + },
  201 + });
  202 +</script>
... ...
src/views/project/finance/financeProfit/ServiceProfit/PackageProfit/data.tsx
... ... @@ -182,9 +182,23 @@ export const COLUMNS = [
182 182 width: 140,
183 183 customRender: (column) => {
184 184 if (column.record?.packetProfitRate) {
185   - return column.record?.packetProfitRate?.toFixed(2) + '%';
  185 + return (column.record?.packetProfitRate * 100).toFixed(2) + '%';
186 186 }
187 187 return column.record?.packetProfitRate?.toFixed(2);
188 188 },
189 189 },
  190 + {
  191 + title: '状态',
  192 + dataIndex: 'status',
  193 + width: 120,
  194 + customRender: (column) => {
  195 + if (column.record?.packStatus === null || column.record?.packStatus === -1) {
  196 + return '未完成';
  197 + } else if (column.record?.packStatus === 0) {
  198 + return '待审核';
  199 + } else if (column.record?.packStatus === 1) {
  200 + return '已审核';
  201 + }
  202 + },
  203 + },
190 204 ];
191 205 \ No newline at end of file
... ...
src/views/project/finance/financeProfit/ServiceProfit/PackageProfit/index.vue
1 1 <template>
2 2 <div>
3 3 <BasicTable @register="registerTable" :bordered="true">
  4 + <template #headerTop>
  5 + <a-alert type="info" show-icon>
  6 + <template #message>
  7 + <template v-if="checkedKeys.length > 0">
  8 + <span>已选中{{ checkedKeys.length }}条记录(可跨页)</span>
  9 + <a-button
  10 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  11 + type="link"
  12 + @click="handleClearChoose"
  13 + size="small"
  14 + >清空</a-button
  15 + >
  16 + </template>
  17 + <template v-else>
  18 + <span>未选中任何订单</span>
  19 + </template>
  20 + </template>
  21 + </a-alert>
  22 + </template>
4 23 <template #bodyCell="{ column, record }">
5 24 <template v-if="column.key === 'picUrl'">
6 25 <img :z-index="100000" :width="50" :height="50" :src="record?.picUrl" />
7 26 </template>
8 27 <template v-if="column.key === 'action'">
9   - <TableAction :actions="createActions(record)" />
  28 + <TableAction
  29 + :actions="createActions(record)"
  30 + :dropDownActions="createDropActions(record)"
  31 + />
10 32 </template>
11 33 <!-- <template v-if="column.key === 'relationValue'">
12 34 <a-input
... ... @@ -19,6 +41,15 @@
19 41 </span>
20 42 </template> -->
21 43 </template>
  44 + <template #toolbar>
  45 + <a-button
  46 + type="primary"
  47 + @click="handleExport"
  48 + v-if="role == ROLE.ADMIN || role == ROLE.FINANCE"
  49 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  50 + >导出</a-button
  51 + >
  52 + </template>
22 53 </BasicTable>
23 54 <!-- <BasicModal
24 55 title="拒绝原因"
... ... @@ -34,27 +65,47 @@
34 65 </BasicModal> -->
35 66 <CheckDetail @register="checkModalRegister" :onGoFormDetail="handleGoFormDetail" />
36 67 <FinanceEdit @register="registerFinanceEdit" @success="handleSuccess" />
  68 + <HistoryDetail @register="registerHistoryDetail" />
37 69 </div>
38 70 </template>
39 71 <script setup lang="ts">
40 72 import { BasicTable, useTable, TableAction } from '/@/components/Table';
41   - import { getPackageProfit } from '@/api/project/invoice';
  73 + import { getPackageProfit, setPackStatus } from '@/api/project/invoice';
42 74 import { searchFormSchema, COLUMNS } from './data';
43 75 import { BasicModal, useModal } from '/@/components/Modal';
44 76 import { useMessage } from '/@/hooks/web/useMessage';
45   - import { onMounted, ref } from 'vue';
  77 + import { onMounted, ref, computed } from 'vue';
46 78 import { useDrawer } from '/@/components/Drawer';
47 79 import FinanceEdit from './FinanceEdit.vue';
48 80 import CheckDetail from './CheckDetail.vue';
  81 + import HistoryDetail from './HistoryDetail.vue';
  82 + import axios from 'axios';
49 83 import { FilePptOutlined } from '@ant-design/icons-vue';
50 84 import { useOrderStoreWithOut } from '/@/store/modules/order';
  85 + import { useUserStoreWithOut } from '/@/store/modules/user';
  86 + import { RoleEnum } from '/@/enums/roleEnum';
  87 + import { ROLE } from '../../../type.d';
51 88  
52 89 const { createMessage } = useMessage();
53 90 const { error } = createMessage;
54 91 const message = ref();
  92 + const checkedKeys = ref<string[]>([]);
  93 + // Define arrays to store collected data from selected rows
  94 + const orderIds = ref<number[]>([]);
  95 + const projectNo = ref<string[]>([]);
  96 + const customerCode = ref<string[]>([]);
  97 + const innerNo = ref<string[]>([]);
  98 + const productionDepartment = ref<string[]>([]);
  99 + const detailProjectNoKeys = ref<string[]>([]);
55 100 const orderStore = useOrderStoreWithOut();
56 101 const [checkModalRegister, { openDrawer: openCheckDetailDrawer }] = useDrawer();
57 102 const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer();
  103 + const [registerHistoryDetail, { openDrawer: openHistoryDetail }] = useDrawer();
  104 + const userStore = useUserStoreWithOut();
  105 + const user = userStore.getUserInfo;
  106 + const role = computed(() => {
  107 + return user?.roleSmallVO?.code;
  108 + });
58 109 const handleOk = (record) => {
59 110 // 修改父组件的状态
60 111 closeModal();
... ... @@ -64,16 +115,34 @@
64 115 message.value = '';
65 116 }
66 117 };
67   - const [registerTable, { reload }] = useTable({
  118 + const [registerTable, { reload, setSelectedRowKeys, getForm }] = useTable({
68 119 api: getPackageProfit,
69 120 bordered: true,
70 121 columns: COLUMNS,
71   - rowKey: 'id',
  122 + rowKey: (record) => record.orderId,
72 123 formConfig: {
73 124 labelWidth: 120,
74 125 schemas: searchFormSchema,
75 126 autoSubmitOnEnter: true,
76 127 },
  128 + // Add custom row props to prevent selection when clicking rows
  129 + customRow: () => {
  130 + return {
  131 + onClick: (e) => {
  132 + // Prevent row selection when clicking anywhere except checkboxes
  133 + if (!e.target.closest('.ant-checkbox-wrapper')) {
  134 + e.stopPropagation();
  135 + }
  136 + },
  137 + };
  138 + },
  139 + rowSelection: {
  140 + type: 'checkbox',
  141 + selectedRowKeys: checkedKeys as any,
  142 + onSelect: onSelect,
  143 + onSelectAll: onSelectAll,
  144 + checkStrictly: true,
  145 + },
77 146 useSearchForm: true,
78 147 showTableSetting: true,
79 148 showIndexColumn: false,
... ... @@ -81,7 +150,7 @@
81 150 setting: false,
82 151 },
83 152 actionColumn: {
84   - width: 240,
  153 + width: 260,
85 154 title: 'Action',
86 155 dataIndex: 'action',
87 156 },
... ... @@ -113,6 +182,18 @@
113 182 // },
114 183 onClick: handleFalse.bind(null, record),
115 184 },
  185 + {
  186 + label: '审核通过',
  187 + popConfirm: {
  188 + title: '确认审核?',
  189 + confirm: () => {
  190 + if (record.packStatus === 0 && role.value === ROLE.ADMIN) {
  191 + handleStatus(record, true);
  192 + }
  193 + return;
  194 + },
  195 + },
  196 + },
116 197 ];
117 198 }
118 199 return [
... ... @@ -130,9 +211,17 @@
130 211 ];
131 212 }
132 213  
133   - onMounted(async () => {
134   - await orderStore.getDict();
135   - });
  214 + function createDropActions(record: any) {
  215 + if (!record.editable) {
  216 + const actions = [
  217 + {
  218 + label: '历史记录',
  219 + onClick: handleHistoryDetail.bind(null, record),
  220 + },
  221 + ];
  222 + return actions;
  223 + }
  224 + }
136 225  
137 226 function handleFinanceEdit(record) {
138 227 openFinanceEdit(true, {
... ... @@ -176,5 +265,218 @@
176 265 // await deleteConfig({ ids: [record.id] });
177 266 // reload();
178 267 // }
  268 +
  269 + async function handleStatus(record, status) {
  270 + try {
  271 + await setPackStatus({ orderId: record.orderId });
  272 + reload();
  273 + } catch (error) {
  274 + console.error(error);
  275 + }
  276 + }
  277 +
  278 + function handleHistoryDetail(record) {
  279 + openHistoryDetail(true, {
  280 + data: record,
  281 + });
  282 + }
  283 +
  284 + function handleGoFormDetail() {
  285 + // Add your logic for form detail navigation
  286 + }
  287 +
  288 + onMounted(async () => {
  289 + await orderStore.getDict();
  290 + });
  291 +
  292 + function handleClearChoose() {
  293 + checkedKeys.value = [];
  294 + // Clear all collected data arrays
  295 + orderIds.value = [];
  296 + projectNo.value = [];
  297 + customerCode.value = [];
  298 + innerNo.value = [];
  299 + productionDepartment.value = [];
  300 + detailProjectNoKeys.value = []; // Add this line
  301 + }
  302 +
  303 + function handleExport() {
  304 + // Get current search parameters from the form
  305 + const values = getForm().getFieldsValue();
  306 +
  307 + // Create export parameters based on whether orderIds are selected
  308 + let exportParams;
  309 +
  310 + // If orderIds are selected, only use those and ignore search params
  311 + if (orderIds.value.length > 0) {
  312 + exportParams = {
  313 + orderIds: orderIds.value
  314 + };
  315 + }
  316 + // Otherwise use the search parameters
  317 + else {
  318 + exportParams = {
  319 + ...values,
  320 + projectNo: values.projectNo || undefined,
  321 + customerCode: values.customerCode || undefined,
  322 + innerNo: values.innerNo || undefined,
  323 + productionDepartment: values.productionDepartment || undefined
  324 + };
  325 + }
  326 +
  327 + console.log('Export parameters:', exportParams);
  328 +
  329 + const token = userStore.getToken;
  330 + axios
  331 + .post(
  332 + '/basic-api/order/cost/businessProfitDetail/exportExcel',
  333 + exportParams,
  334 + {
  335 + headers: {
  336 + Authorization: `${token}`, // 去掉引号
  337 + },
  338 + responseType: 'blob', // 设置响应类型为 'blob'
  339 + },
  340 + )
  341 + .then((response) => {
  342 + // 创建一个 Blob 对象来保存二进制数据
  343 + const blob = new Blob([response.data], { type: 'application/zip' });
  344 + const getFormattedDate = (): string => {
  345 + const date = new Date();
  346 +
  347 + const year = date.getFullYear();
  348 + const month = String(date.getMonth() + 1).padStart(2, '0');
  349 + const day = String(date.getDate()).padStart(2, '0');
  350 +
  351 + const hours = String(date.getHours()).padStart(2, '0');
  352 + const minutes = String(date.getMinutes()).padStart(2, '0');
  353 + const seconds = String(date.getSeconds()).padStart(2, '0');
  354 +
  355 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  356 + };
  357 + const date = getFormattedDate();
  358 + // 创建一个链接元素用于下载
  359 + const link = document.createElement('a');
  360 + link.href = window.URL.createObjectURL(blob);
  361 + link.download = `业务利润分析表${date}.xlsx`; // 你可以为文件命名
  362 + document.body.appendChild(link);
  363 + link.click(); // 自动点击链接,触发下载
  364 + document.body.removeChild(link); // 下载完成后移除链接
  365 + })
  366 + .catch((error) => {
  367 + console.error(error);
  368 + });
  369 + handleClearChoose();
  370 + reload();
  371 + }
  372 + async function onSelect(record, selected: boolean, _selectedRows, nativeEvent) {
  373 + // Only process selection if it's coming from a checkbox click or programmatically
  374 + // This checks if the event is from clicking the row - then we ignore it
  375 + if (nativeEvent && nativeEvent.target && !nativeEvent.target.closest('.ant-checkbox-wrapper')) {
  376 + return;
  377 + }
  378 +
  379 + const rowKey = record.orderId;
  380 + if (selected) {
  381 + checkedKeys.value = [...checkedKeys.value, rowKey];
  382 +
  383 + // Add data from the selected row to the corresponding arrays
  384 + if (record.orderId !== undefined && !orderIds.value.includes(record.orderId)) {
  385 + orderIds.value.push(record.orderId);
  386 + }
  387 +
  388 + if (record.projectNo !== undefined && !projectNo.value.includes(record.projectNo)) {
  389 + projectNo.value.push(record.projectNo);
  390 + }
  391 +
  392 + if (record.customerCode !== undefined && !customerCode.value.includes(record.customerCode)) {
  393 + customerCode.value.push(record.customerCode);
  394 + }
  395 +
  396 + if (record.innerNo !== undefined && !innerNo.value.includes(record.innerNo)) {
  397 + innerNo.value.push(record.innerNo);
  398 + }
  399 +
  400 + if (record.productionDepartment !== undefined && !productionDepartment.value.includes(record.productionDepartment)) {
  401 + productionDepartment.value.push(record.productionDepartment);
  402 + }
  403 +
  404 + if (record.detailProjectNoKey !== undefined && !detailProjectNoKeys.value.includes(record.detailProjectNoKey)) {
  405 + detailProjectNoKeys.value.push(record.detailProjectNoKey);
  406 + }
  407 + } else {
  408 + checkedKeys.value = checkedKeys.value.filter((key) => key !== rowKey);
  409 +
  410 + // Remove data from the deselected row from the corresponding arrays
  411 + if (record.orderId !== undefined) {
  412 + orderIds.value = orderIds.value.filter(id => id !== record.orderId);
  413 + }
  414 +
  415 + if (record.projectNo !== undefined) {
  416 + projectNo.value = projectNo.value.filter(no => no !== record.projectNo);
  417 + }
  418 +
  419 + if (record.customerCode !== undefined) {
  420 + customerCode.value = customerCode.value.filter(code => code !== record.customerCode);
  421 + }
  422 +
  423 + if (record.innerNo !== undefined) {
  424 + innerNo.value = innerNo.value.filter(no => no !== record.innerNo);
  425 + }
  426 +
  427 + if (record.productionDepartment !== undefined) {
  428 + productionDepartment.value = productionDepartment.value.filter(dept => dept !== record.productionDepartment);
  429 + }
  430 + }
  431 + setSelectedRowKeys(checkedKeys.value as any);
  432 + }
  433 +
  434 + function onSelectAll(selected: boolean, selectedRows: any[]) {
  435 + if (selected) {
  436 + // First clear all previous selections to avoid duplicates
  437 + checkedKeys.value = [];
  438 + orderIds.value = [];
  439 + projectNo.value = [];
  440 + customerCode.value = [];
  441 + innerNo.value = [];
  442 + productionDepartment.value = [];
  443 +
  444 + // Add all selected rows
  445 + selectedRows.forEach((row) => {
  446 + const rowKey = row.orderId;
  447 + checkedKeys.value.push(rowKey);
  448 +
  449 + // Add data from each selected row to the corresponding arrays
  450 + if (row.orderId !== undefined) {
  451 + orderIds.value.push(row.orderId);
  452 + }
  453 +
  454 + if (row.projectNo !== undefined) {
  455 + projectNo.value.push(row.projectNo);
  456 + }
  457 +
  458 + if (row.customerCode !== undefined) {
  459 + customerCode.value.push(row.customerCode);
  460 + }
  461 +
  462 + if (row.innerNo !== undefined) {
  463 + innerNo.value.push(row.innerNo);
  464 + }
  465 +
  466 + if (row.productionDepartment !== undefined) {
  467 + productionDepartment.value.push(row.productionDepartment);
  468 + }
  469 + });
  470 + } else {
  471 + // Clear all selections when deselecting all
  472 + checkedKeys.value = [];
  473 + orderIds.value = [];
  474 + projectNo.value = [];
  475 + customerCode.value = [];
  476 + innerNo.value = [];
  477 + productionDepartment.value = [];
  478 + }
  479 + setSelectedRowKeys(checkedKeys.value as any);
  480 + }
179 481 </script>
180 482 <style></style>
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/ApproveReason.vue
... ... @@ -37,6 +37,7 @@
37 37 projectEndTime: baseFieldValues.value.projectEndTime,
38 38 spainPaidRmbCommission: baseFieldValues.value.spainPaidRmbCommission,
39 39 applyRemark: baseFieldValues.value.applyRemark,
  40 + type: 'FIELD_EDIT_APPLY',
40 41 });
41 42 emit('success');
42 43 setTimeout(() => {
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/FinanceEdit.vue
... ... @@ -9,6 +9,7 @@
9 9 @ok="handleSubmit"
10 10 :showDetailBack="false"
11 11 okText="保存"
  12 + @visible-change="handleShow"
12 13 showFooter
13 14 :destroyOnClose="true"
14 15 >
... ... @@ -45,14 +46,6 @@
45 46 auto-size
46 47 />
47 48 <div style="margin: 16px 0"></div>
48   - <div style="font-size: 15px">实际汇率¥</div>
49   - <a-input
50   - v-model:value="input6"
51   - placeholder="请输入"
52   - :disabled="status6 === 'LOCKED'"
53   - auto-size
54   - />
55   - <div style="margin: 16px 0"></div>
56 49 <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
57 50 <template #appendFooter>
58 51 <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
... ... @@ -145,16 +138,13 @@
145 138 const status3 = ref();
146 139 const status4 = ref();
147 140 const status5 = ref();
148   - const status6 = ref();
149 141  
150 142 const input1 = ref();
151 143 const input2 = ref();
152 144 const input3 = ref();
153   - // const input4 = ref();
154   - const input4 = ref(dayjs('2013-12-01'));
  145 + const input4 = ref();
155 146  
156 147 const input5 = ref();
157   - const input6 = ref();
158 148 const id = ref();
159 149 // function formatDate(dateStr: string): string {
160 150 // const date = new Date(dateStr);
... ... @@ -185,16 +175,14 @@
185 175 status3.value = data?.data?.lockFields?.projectStartTime;
186 176 status4.value = data?.data?.lockFields?.projectEndTime;
187 177 status5.value = data?.data?.lockFields?.paidRmbCommission;
188   - status6.value = data?.data?.lockFields?.actualExchangeRate;
189 178 }
190   - console.log(data.data.lockFields, '5656vdata.data.lockFields');
191 179 id.value = data?.data?.projectNoPrefix;
192 180 input1.value = data?.data?.developmentCopyRmbTotalPrice.toFixed(2);
193 181 input2.value = data?.data?.spainPaidRmbCommission.toFixed(2);
194 182 input3.value = dayjs(formatDateToDateOnly(data?.data?.projectStartTime));
195 183 input4.value = dayjs(formatDateToDateOnly(data?.data?.projectEndTime));
196 184 input5.value = data?.data?.paidRmbCommission.toFixed(2);
197   - input6.value = data?.data?.actualExchangeRate.toFixed(2);
  185 +
198 186 resetFields();
199 187 setDrawerProps({ confirmLoading: false });
200 188 setFieldsValue({
... ... @@ -220,10 +208,18 @@
220 208 projectStartTime: input3.value,
221 209 projectEndTime: input4.value,
222 210 paidRmbCommission: input5.value,
223   - actualExchangeRate: input6.value,
224 211 });
225 212 emit('success');
226 213 closeDrawer();
227 214 }
228 215 }
  216 + function handleShow(visible: boolean) {
  217 + if (!visible) {
  218 + input1.value = '';
  219 + input2.value = '';
  220 + input3.value = '';
  221 + input4.value = '';
  222 + input5.value = '';
  223 + }
  224 + }
229 225 </script>
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/HistoryDetail.vue 0 → 100644
  1 +<template>
  2 + <BasicDrawer
  3 + @register="register"
  4 + v-bind="$attrs"
  5 + title="操作记录"
  6 + width="60%"
  7 + :isDetail="true"
  8 + :showDetailBack="false"
  9 + okText="保存"
  10 + :destroyOnClose="true"
  11 + >
  12 + <Tabs v-model:activeKey="activeKey" className="my-0">
  13 + <TabPanel :key="1" tab="操作记录" className="w-full">
  14 + <a-list :pagination="pagination1" className="w-full">
  15 + <template v-for="item in list1" :key="item.id">
  16 + <a-list-item class="list">
  17 + <a-list-item-meta>
  18 + <template #avatar> </template>
  19 + <template #title>
  20 + <span>{{ item.userName }}</span>
  21 + </template>
  22 + <template #description>
  23 + <div class="description">
  24 + {{ item.optType }}
  25 + </div>
  26 + <div class="info">
  27 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  28 + </div>
  29 + </template>
  30 + </a-list-item-meta>
  31 + </a-list-item>
  32 + </template>
  33 + </a-list>
  34 + </TabPanel>
  35 + <TabPanel :key="2" tab="审批记录" className="w-full">
  36 + <a-list :pagination="pagination2" className="w-full">
  37 + <template v-for="item in list2" :key="item.id">
  38 + <a-list-item class="list">
  39 + <a-list-item-meta>
  40 + <template #avatar> </template>
  41 + <template #title>
  42 + <span>{{ item.userName }}</span>
  43 + </template>
  44 + <template #description>
  45 + <div class="description">
  46 + {{ item.optType }}
  47 + </div>
  48 + <div class="info">
  49 + <div><span>操作时间:</span>{{ formatToDateTime(item.createTime) }}</div>
  50 + </div>
  51 + </template>
  52 + </a-list-item-meta>
  53 + </a-list-item>
  54 + </template>
  55 + </a-list>
  56 + </TabPanel>
  57 + </Tabs>
  58 +
  59 + <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> -->
  60 + <template #appendFooter>
  61 + <!-- <a-button type="primary" @click="onGoCheckDetail"> 申请权限</a-button> -->
  62 + </template>
  63 + </BasicDrawer>
  64 +</template>
  65 +<script lang="ts">
  66 + import { defineComponent, ref, computed } from 'vue';
  67 + import { Tabs, Progress, Row, Col, List } from 'ant-design-vue';
  68 + import { FormSchema, useForm } from '/@/components/Form/index';
  69 +
  70 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  71 + import { getProjectOptLog } from '/@/api/project/invoice';
  72 + import { formatToDateTime } from '/@/utils/dateUtil';
  73 +
  74 + const TabPanel = Tabs.TabPane;
  75 +
  76 + const schemas: FormSchema[] = [
  77 + {
  78 + field: '订单号',
  79 + component: 'Input',
  80 + label: '字段1',
  81 + componentProps: {
  82 + readonly: true,
  83 + disabled: true,
  84 + },
  85 + colProps: {
  86 + span: 12,
  87 + },
  88 + defaultValue: '111',
  89 + },
  90 + {
  91 + field: 'field2',
  92 + component: 'Input',
  93 + label: '字段2',
  94 + colProps: {
  95 + span: 12,
  96 + },
  97 + },
  98 + ];
  99 + const achieveList = [
  100 + {
  101 + key: '1',
  102 + name: '操作记录',
  103 + },
  104 + {
  105 + key: '2',
  106 + name: '审批记录',
  107 + },
  108 + ];
  109 + export default defineComponent({
  110 + components: {
  111 + BasicDrawer,
  112 + Tabs,
  113 + TabPanel,
  114 + [List.name]: List,
  115 + [List.Item.name]: List.Item,
  116 + AListItemMeta: List.Item.Meta,
  117 + },
  118 + props: {
  119 + onGoCheckDetail: {
  120 + type: Function,
  121 + },
  122 + },
  123 + setup() {
  124 + const list1 = ref([]);
  125 + const total1 = ref(0);
  126 + const page1 = ref(1);
  127 +
  128 + const list2 = ref([]);
  129 + const total2 = ref(0);
  130 + const page2 = ref(1);
  131 + const orderId = ref('');
  132 + const activeKey = ref(1);
  133 +
  134 + const getOrderOptLogFunc = async (data, index, page) => {
  135 + console.log('%c [ data ]-135', 'font-size:13px; background:pink; color:#bf2c9f;', data);
  136 + if (index === 1) {
  137 + const res = await getProjectOptLog({
  138 + projectNoPrefix: data,
  139 + type: [10],
  140 + page: page,
  141 + pageSize: 20
  142 + });
  143 + list1.value = res.items;
  144 + total1.value = res.total;
  145 + page1.value = page;
  146 + } else {
  147 + const res = await getProjectOptLog({
  148 + projectNoPrefix: data,
  149 + type: [0,2],
  150 + page: page,
  151 + pageSize: 20
  152 + });
  153 + list2.value = res.items;
  154 + total2.value = res.total;
  155 + page2.value = page;
  156 + }
  157 + };
  158 + const [register] = useDrawerInner((data) => {
  159 + orderId.value = data.data.projectNoPrefix;
  160 + getOrderOptLogFunc(orderId.value, 1, 1);
  161 + getOrderOptLogFunc(orderId.value, 2, 1);
  162 + });
  163 +
  164 + const pagination1 = computed(() => {
  165 + return {
  166 + show: true,
  167 + pageSize: 20,
  168 + page: page1.value,
  169 + total: total1.value,
  170 + onChange(cur) {
  171 + getOrderOptLogFunc(orderId.value, 1, cur);
  172 + },
  173 + };
  174 + });
  175 +
  176 + const pagination2 = computed(() => {
  177 + return {
  178 + show: true,
  179 + pageSize: 20,
  180 + page: page1.value,
  181 + total: total1.value,
  182 + onChange(cur) {
  183 + getOrderOptLogFunc(orderId.value, 2, cur);
  184 + },
  185 + };
  186 + });
  187 +
  188 + return {
  189 + register,
  190 + schemas,
  191 + achieveList,
  192 + list1,
  193 + list2,
  194 + prefixCls: 'account-center',
  195 + pagination1,
  196 + pagination2,
  197 + activeKey,
  198 + formatToDateTime,
  199 + };
  200 + },
  201 + });
  202 +</script>
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/data.tsx
... ... @@ -104,6 +104,14 @@ export const COLUMNS = [
104 104 width: 100,
105 105 },
106 106 {
  107 + title: '订单总数量',
  108 + dataIndex: 'orderCount',
  109 + width: 120,
  110 + customRender: (column) => {
  111 + return column.record?.orderCount;
  112 + },
  113 + },
  114 + {
107 115 title: '客户总金额¥',
108 116 width: 150,
109 117 dataIndex: 'customerTotalPrice',
... ... @@ -132,7 +140,7 @@ export const COLUMNS = [
132 140 dataIndex: 'packetTotalPrice',
133 141 width: 150,
134 142 customRender: (column) => {
135   - return column.record?.packetTotalPrice?.toFixed(2);
  143 + return column.record?.packetRmbTotalPrice?.toFixed(2);
136 144 },
137 145 },
138 146 {
... ... @@ -229,7 +237,7 @@ export const COLUMNS = [
229 237 width: 120,
230 238 customRender: (column) => {
231 239 if (column.record?.profitRate) {
232   - return column.record?.profitRate?.toFixed(2) + '%';
  240 + return (column.record?.profitRate * 100).toFixed(2) + '%';
233 241 }
234 242 return column.record?.profitRate?.toFixed(2);
235 243 },
... ... @@ -248,7 +256,7 @@ export const COLUMNS = [
248 256 width: 120,
249 257 customRender: (column) => {
250 258 if (column.record?.developmentProfitRate) {
251   - return column.record?.developmentProfitRate?.toFixed(2) + '%';
  259 + return (column.record?.developmentProfitRate * 100).toFixed(2) + '%';
252 260 }
253 261 return column.record?.developmentProfitRate?.toFixed(2);
254 262 },
... ... @@ -270,14 +278,6 @@ export const COLUMNS = [
270 278 },
271 279 },
272 280 {
273   - title: '订单总数量',
274   - dataIndex: 'orderCount',
275   - width: 120,
276   - customRender: (column) => {
277   - return column.record?.orderCount;
278   - },
279   - },
280   - {
281 281 title: '实际跟单单价¥',
282 282 dataIndex: 'actualOrderRmbPrice',
283 283 width: 160,
... ... @@ -302,22 +302,6 @@ export const COLUMNS = [
302 302 },
303 303 },
304 304 {
305   - title: '实际汇率¥',
306   - dataIndex: 'actualExchangeRate',
307   - width: 120,
308   - customRender: (column) => {
309   - return column.record?.actualExchangeRate?.toFixed(2);
310   - },
311   - },
312   - {
313   - title: '汇率收益¥',
314   - dataIndex: 'exchangeRateProfit',
315   - width: 120,
316   - customRender: (column) => {
317   - return column.record?.exchangeRateProfit?.toFixed(2);
318   - },
319   - },
320   - {
321 305 title: '综合收益¥',
322 306 dataIndex: 'comprehensiveProfit',
323 307 width: 120,
... ... @@ -380,4 +364,18 @@ export const COLUMNS = [
380 364 return <FilePptOutlined style="font-size:25px" onClick={() => handleClick()} />;
381 365 },
382 366 },
  367 + {
  368 + title: '状态',
  369 + dataIndex: 'status',
  370 + width: 120,
  371 + customRender: (column) => {
  372 + if (column.record?.developmentStatus === null || column.record?.developmentStatus === -1) {
  373 + return '未完成';
  374 + } else if (column.record?.developmentStatus === 0) {
  375 + return '待审核';
  376 + } else if (column.record?.developmentStatus === 1) {
  377 + return '已审核';
  378 + }
  379 + },
  380 + },
383 381 ];
384 382 \ No newline at end of file
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/index.vue
1 1 <template>
2 2 <div>
3 3 <BasicTable @register="registerTable" :bordered="true">
  4 + <template #headerTop>
  5 + <a-alert type="info" show-icon>
  6 + <template #message>
  7 + <template v-if="checkedKeys.length > 0">
  8 + <span>已选中{{ checkedKeys.length }}条记录(可跨页)</span>
  9 + <a-button
  10 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  11 + type="link"
  12 + @click="handleClearChoose"
  13 + size="small"
  14 + >清空</a-button
  15 + >
  16 + </template>
  17 + <template v-else>
  18 + <span>未选中任何订单</span>
  19 + </template>
  20 + </template>
  21 + </a-alert>
  22 + </template>
4 23 <template #bodyCell="{ column, record }">
5 24 <template v-if="column.key === 'action'">
6   - <TableAction :actions="createActions(record)" />
7   - </template>
8   - <!-- <template v-if="column.key === 'relationValue'">
9   - <a-input
10   - v-if="record.settingValue === 'A01'"
11   - v-model:value="record.settingValue"
12   - :max-length="50"
  25 + <TableAction
  26 + :actions="createActions(record)"
  27 + :dropDownActions="createDropActions(record)"
13 28 />
14   - <span v-else style="color: red">
15   - {{ record.settingValue }}
16   - </span>
17   - </template> -->
  29 + </template>
  30 + </template>
  31 + <template #toolbar>
  32 + <a-button
  33 + type="primary"
  34 + @click="handleExport"
  35 + v-if="role == ROLE.ADMIN || role == ROLE.FINANCE"
  36 + :style="{ borderRadius: '5px 5px 5px 5px' }"
  37 + >导出</a-button
  38 + >
18 39 </template>
19 40 </BasicTable>
20   - <!-- <BasicModal
21   - title="拒绝原因"
22   - width="30%"
23   - @register="registerModal"
24   - @visible-change="handleClose"
25   - @ok="handleOk"
26   - wrapClassName="approve-modal"
27   - >
28   - <div className="pa-8">
29   - <a-textarea :rows="6" placeholder="请输入拒绝原因" v-model:value="message" />
30   - </div>
31   - </BasicModal> -->
32 41 <CheckDetail @register="checkModalRegister" :onGoFormDetail="handleGoFormDetail" />
33 42 <FinanceEdit @register="registerFinanceEdit" @success="handleSuccess" />
  43 + <HistoryDetail @register="registerHistoryDetail" />
34 44 </div>
35 45 </template>
36 46 <script setup lang="ts">
37 47 import { BasicTable, useTable, TableAction } from '/@/components/Table';
38   - import { getServiceProfit } from '@/api/project/invoice';
  48 + import { getServiceProfit, setBusinessProfitSetStatus } from '@/api/project/invoice';
39 49 import { searchFormSchema, COLUMNS } from './data';
40   - import { BasicModal, useModal } from '/@/components/Modal';
  50 + import axios from 'axios';
41 51 import { useMessage } from '/@/hooks/web/useMessage';
42   - import { onMounted, ref } from 'vue';
  52 + import { onMounted, ref, computed, unref } from 'vue';
43 53 import { useDrawer } from '/@/components/Drawer';
44 54 import FinanceEdit from './FinanceEdit.vue';
  55 + import HistoryDetail from './HistoryDetail.vue';
45 56 import CheckDetail from './CheckDetail.vue';
  57 + import { useUserStoreWithOut } from '/@/store/modules/user';
  58 + import { ROLE } from '../../../type.d';
46 59 import { useOrderStoreWithOut } from '/@/store/modules/order';
47 60  
48 61 const { createMessage } = useMessage();
49   - const { error } = createMessage;
50 62 const message = ref();
  63 + const checkedKeys = ref<string[]>([]);
  64 + const invoiceIdKeys = ref<string[]>([]);
  65 + const checkIdKeys = ref<string[]>([]);
  66 + const detailProjectNoKeys = ref<string[]>([]);
51 67 const orderStore = useOrderStoreWithOut();
52 68 const [checkModalRegister, { openDrawer: openCheckDetailDrawer }] = useDrawer();
53 69 const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer();
54   - const handleClose = (visible: boolean) => {
55   - if (!visible) {
56   - message.value = '';
57   - }
58   - };
59   - const [registerTable, { reload }] = useTable({
  70 + const [registerHistoryDetail, { openDrawer: openHistoryDetail }] = useDrawer();
  71 + const userStore = useUserStoreWithOut();
  72 + const user = userStore.getUserInfo;
  73 + const role = computed(() => {
  74 + return user?.roleSmallVO?.code;
  75 + });
  76 + const [registerTable, { reload, getSelectRowKeys, getDataSource, setSelectedRowKeys }] = useTable({
  77 + title: '',
60 78 api: getServiceProfit,
61 79 bordered: true,
62 80 columns: COLUMNS,
63 81 clickToRowSelect: false,
64   - rowKey: 'id',
65 82 formConfig: {
66 83 labelWidth: 120,
67 84 schemas: searchFormSchema,
68 85 autoSubmitOnEnter: true,
69 86 },
  87 + rowKey: (record) => record.detailProjectNo || record.id || record.serialNumber,
  88 + rowSelection: {
  89 + type: 'checkbox',
  90 + selectedRowKeys: checkedKeys as any,
  91 + onSelect: onSelect,
  92 + onSelectAll: onSelectAll,
  93 + },
70 94 useSearchForm: true,
71 95 showTableSetting: true,
72 96 showIndexColumn: false,
... ... @@ -74,7 +98,7 @@
74 98 setting: false,
75 99 },
76 100 actionColumn: {
77   - width: 240,
  101 + width: 260,
78 102 title: 'Action',
79 103 dataIndex: 'action',
80 104 },
... ... @@ -82,31 +106,29 @@
82 106  
83 107 function createActions(record: any): any[] {
84 108 if (!record.editable) {
85   - return [
  109 + const actions = [
86 110 {
87 111 label: '财务编辑',
88 112 onClick: handleFinanceEdit.bind(null, record),
89 113 },
90   - // {
91   - // label: '编辑',
92   - // onClick: handleEdit.bind(null, record),
93   - // },
94   - // {
95   - // label: '删除',
96   - // popConfirm: {
97   - // title: '确认删除?',
98   - // confirm: handleDelete.bind(null, record),
99   - // },
100   - // },
101 114 {
102 115 label: '申请权限',
103   - // popConfirm: {
104   - // title: '确认申请?',
105   - // confirm: handleFalse.bind(null, record),
106   - // },
107 116 onClick: handleFalse.bind(null, record),
108 117 },
  118 + {
  119 + label: '审核通过',
  120 + popConfirm: {
  121 + title: '确认审核?',
  122 + confirm: () => {
  123 + if (record.developmentStatus === 0 && role.value === ROLE.ADMIN) {
  124 + handleStatus(record, true);
  125 + }
  126 + return;
  127 + },
  128 + },
  129 + },
109 130 ];
  131 + return actions;
110 132 }
111 133 return [
112 134 {
... ... @@ -122,6 +144,17 @@
122 144 },
123 145 ];
124 146 }
  147 + function createDropActions(record: any) {
  148 + if (!record.editable) {
  149 + const actions = [
  150 + {
  151 + label: '历史记录',
  152 + onClick: handleHistoryDetail.bind(null, record),
  153 + },
  154 + ];
  155 + return actions;
  156 + }
  157 + }
125 158  
126 159 onMounted(async () => {
127 160 await orderStore.getDict();
... ... @@ -139,11 +172,6 @@
139 172 return false;
140 173 }
141 174  
142   - // async function handleFalse(record: any) {
143   - // console.log(record);
144   - // // openModal(true, { record });
145   - // }
146   -
147 175 function handleSuccess() {
148 176 setTimeout(() => {
149 177 reload();
... ... @@ -155,21 +183,185 @@
155 183 handleCancel(record);
156 184 reload();
157 185 }
158   - function handleEdit(record: any) {
159   - // if (record.settingValue == 'A01') {
160   - // error('请勿连续点击生成按钮,需要等待三秒再点击生成');
161   - // } else {
162   - record.onEdit?.(true);
163   - // }
164   - }
165 186  
166 187 function handleCancel(record) {
167 188 record.onEdit?.(false, false);
168 189 }
169 190  
170   - async function handleDelete(record) {
171   - // await deleteConfig({ ids: [record.id] });
  191 + async function handleStatus(record, status) {
  192 + try {
  193 + await setBusinessProfitSetStatus({
  194 + customerCode: record.customerCode,
  195 + projectNo: record.projectNoPrefix,
  196 + });
  197 + reload();
  198 + } catch (error) {
  199 + console.error('Error updating status:', error);
  200 + }
  201 + }
  202 +
  203 + async function handleHistoryDetail(record) {
  204 + openHistoryDetail(true, {
  205 + data: record,
  206 + });
  207 + }
  208 +
  209 + function handleExport() {
  210 + if (checkedKeys.value.length <= 0) {
  211 + createMessage.warn('请选择数据!');
  212 + return;
  213 + }
  214 + const token = userStore.getToken;
  215 + axios
  216 + .post(
  217 + '/basic-api/project/businessProfit/exportExcel',
  218 + { detailProjectNo: detailProjectNoKeys.value },
  219 + {
  220 + headers: {
  221 + Authorization: `${token}`, // 去掉引号
  222 + },
  223 + responseType: 'blob', // 设置响应类型为 'blob'
  224 + },
  225 + )
  226 + .then((response) => {
  227 + // 创建一个 Blob 对象来保存二进制数据
  228 + const blob = new Blob([response.data], { type: 'application/zip' });
  229 + const getFormattedDate = (): string => {
  230 + const date = new Date();
  231 +
  232 + const year = date.getFullYear();
  233 + const month = String(date.getMonth() + 1).padStart(2, '0');
  234 + const day = String(date.getDate()).padStart(2, '0');
  235 +
  236 + const hours = String(date.getHours()).padStart(2, '0');
  237 + const minutes = String(date.getMinutes()).padStart(2, '0');
  238 + const seconds = String(date.getSeconds()).padStart(2, '0');
  239 +
  240 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  241 + };
  242 + const date = getFormattedDate();
  243 + // 创建一个链接元素用于下载
  244 + const link = document.createElement('a');
  245 + link.href = window.URL.createObjectURL(blob);
  246 + link.download = `业务利润分析表${date}.xlsx`; // 你可以为文件命名
  247 + document.body.appendChild(link);
  248 + link.click(); // 自动点击链接,触发下载
  249 + document.body.removeChild(link); // 下载完成后移除链接
  250 + })
  251 + .catch((error) => {
  252 + console.error(error);
  253 + });
  254 + handleClearChoose();
172 255 reload();
173 256 }
  257 +
  258 + function handleClearChoose() {
  259 + checkedKeys.value = [];
  260 + detailProjectNoKeys.value = [];
  261 + invoiceIdKeys.value = [];
  262 + checkIdKeys.value = [];
  263 + }
  264 +
  265 + async function onSelect(record, selected: boolean) {
  266 + const rowKey = record.detailProjectNo || record.id || record.serialNumber;
  267 + if (selected) {
  268 + checkedKeys.value = [...checkedKeys.value, rowKey];
  269 +
  270 + // 如果detailProjectNo是数组,将每个元素添加到detailProjectNoKeys中
  271 + if (record.detailProjectNo !== undefined) {
  272 + if (Array.isArray(record.detailProjectNo)) {
  273 + record.detailProjectNo.forEach((projectNo) => {
  274 + if (!detailProjectNoKeys.value.includes(projectNo)) {
  275 + detailProjectNoKeys.value.push(projectNo);
  276 + }
  277 + });
  278 + } else {
  279 + // 如果是单个值,直接添加
  280 + if (!detailProjectNoKeys.value.includes(record.detailProjectNo)) {
  281 + detailProjectNoKeys.value.push(record.detailProjectNo);
  282 + }
  283 + }
  284 + }
  285 +
  286 + if (record.invoiceId !== undefined) {
  287 + invoiceIdKeys.value = [...invoiceIdKeys.value, record.invoiceId];
  288 + }
  289 +
  290 + if (record.checkId !== undefined) {
  291 + checkIdKeys.value = [...checkIdKeys.value, record.checkId];
  292 + }
  293 + } else {
  294 + checkedKeys.value = checkedKeys.value.filter((key) => key !== rowKey);
  295 +
  296 + // 如果detailProjectNo是数组,从detailProjectNoKeys中移除每个元素
  297 + if (record.detailProjectNo !== undefined) {
  298 + if (Array.isArray(record.detailProjectNo)) {
  299 + detailProjectNoKeys.value = detailProjectNoKeys.value.filter(
  300 + (projectNo) => !record.detailProjectNo.includes(projectNo)
  301 + );
  302 + } else {
  303 + // 如果是单个值,直接移除
  304 + detailProjectNoKeys.value = detailProjectNoKeys.value.filter(
  305 + (projectNo) => projectNo !== record.detailProjectNo
  306 + );
  307 + }
  308 + }
  309 +
  310 + if (record.invoiceId !== undefined) {
  311 + invoiceIdKeys.value = invoiceIdKeys.value.filter(
  312 + (invoiceId) => invoiceId !== record.invoiceId,
  313 + );
  314 + }
  315 +
  316 + if (record.checkId !== undefined) {
  317 + checkIdKeys.value = checkIdKeys.value.filter((checkId) => checkId !== record.checkId);
  318 + }
  319 + }
  320 + setSelectedRowKeys(checkedKeys.value as any);
  321 + }
  322 +
  323 + function onSelectAll(selected: boolean, selectedRows: any[]) {
  324 + if (selected) {
  325 + // 先清空之前的选择,避免重复添加
  326 + checkedKeys.value = [];
  327 + detailProjectNoKeys.value = [];
  328 + invoiceIdKeys.value = [];
  329 + checkIdKeys.value = [];
  330 +
  331 + // 重新添加所有选中的行
  332 + selectedRows.forEach((row) => {
  333 + const rowKey = row.detailProjectNo || row.id || row.serialNumber;
  334 + checkedKeys.value.push(rowKey);
  335 +
  336 + // 如果detailProjectNo是数组,将每个元素添加到detailProjectNoKeys中
  337 + if (row.detailProjectNo !== undefined) {
  338 + if (Array.isArray(row.detailProjectNo)) {
  339 + row.detailProjectNo.forEach((projectNo) => {
  340 + // 由于已经清空了数组,不需要检查是否存在
  341 + detailProjectNoKeys.value.push(projectNo);
  342 + });
  343 + } else {
  344 + // 如果是单个值,直接添加
  345 + detailProjectNoKeys.value.push(row.detailProjectNo);
  346 + }
  347 + }
  348 +
  349 + if (row.invoiceId !== undefined) {
  350 + invoiceIdKeys.value.push(row.invoiceId);
  351 + }
  352 +
  353 + if (row.checkId !== undefined) {
  354 + checkIdKeys.value.push(row.checkId);
  355 + }
  356 + });
  357 + } else {
  358 + // 取消全选时,清空所有选择
  359 + checkedKeys.value = [];
  360 + detailProjectNoKeys.value = [];
  361 + invoiceIdKeys.value = [];
  362 + checkIdKeys.value = [];
  363 + }
  364 + setSelectedRowKeys(checkedKeys.value as any);
  365 + }
174 366 </script>
175 367 <style></style>
... ...
src/views/project/finance/financeProfit/ServiceProfit/ServiceProfit/tableData.tsx
... ... @@ -55,12 +55,4 @@ export const FIELDS_BASE_INFO = [
55 55 label: '中国团队已发提成¥',
56 56 rules: [{ required: true }],
57 57 },
58   - {
59   - field: 'actualExchangeRate',
60   - component: 'Select',
61   - labelWidth: 150,
62   - label: '实际汇率¥',
63   - rules: [{ required: true }],
64   - },
65 58 ];
66   -
... ...
src/views/project/finance/pay/index.vue
... ... @@ -322,9 +322,6 @@
322 322 });
323 323 }
324 324  
325   - // console.log(checkedKeys.value, 565666666); // 输出当前的 selectedCustomCodes 值
326   - // console.log(selectedCustomCodes.value, 565666666); // 输出当前的 selectedCustomCodes 值
327   - // console.log(selectedProductionDepartment.value, 565666666); // 输出当前的 selectedCustomCodes 值
328 325 }
329 326  
330 327 function handleFinanceEdit(record) {
... ...
src/views/project/finance/type.d.ts 0 → 100644
  1 +export enum ROLE {
  2 + ADMIN = 'admin', // 超管
  3 + CUSTOM_ADMIN = 'custom_admin', // 客户管理员
  4 + DATA_REPORT_USER = 'data_report_user', //数据分析员
  5 + BUSINESS = 'business_user', // 业务员
  6 + TRACKER = 'tracker_user', // 跟单员
  7 + INSPECT = 'inspect_user', // 质检员
  8 + PRODUCE = 'produce_user', //生产科
  9 + FINANCE = 'finance_user', //财务
  10 +}
... ...
vite.config.ts
... ... @@ -30,26 +30,33 @@ export default defineApplicationConfig({
30 30 },
31 31 },
32 32 '/basic-api/order': {
33   - // target: 'http://localhost:18001',
34   - target: 'http://47.104.8.35:18001',
  33 + target: 'http://47.104.8.35:8000',
  34 + // target: 'http://localhost:18000',
  35 + // target: 'http://39.108.227.113:8000',
  36 + // target: 'http://localhost:8000',
  37 + // target: 'http://39.108.227.113:3000/mock/35',
  38 + // http://39.108.227.113:8000/order/erp/captcha/get_img_captcha_code
  39 + changeOrigin: true,
  40 + ws: true,
  41 + rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''),
  42 + },
  43 + '/basic-api/project': {
  44 + target: 'http://47.104.8.35:8000',
  45 + // target: 'http://localhost:18000',
  46 + // target: 'http://39.108.227.113:8000',
  47 + // target: 'http://localhost:8000',
  48 + // target: 'http://39.108.227.113:3000/mock/35',
  49 + // http://39.108.227.113:8000/order/erp/captcha/get_img_captcha_code
35 50 changeOrigin: true,
36 51 ws: true,
37 52 rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''),
38 53 },
39   - // '/basic-api/project': {
40   - // target: 'http://47.104.8.35:8000',
41   - // // target: 'http://localhost:18000',
42   - // // target: 'http://39.108.227.113:8000',
43   - // // target: 'http://localhost:8000',
44   - // // target: 'http://39.108.227.113:3000/mock/35',
45   - // // http://39.108.227.113:8000/order/erp/captcha/get_img_captcha_code
46   - // changeOrigin: true,
47   - // ws: true,
48   - // rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''),
49   - // },
50 54 '/api/localStorage/upload': {
51   - // target: 'http://localhost:18001',
52   - target: 'http://47.104.8.35:18001',
  55 + target: 'http://47.104.8.35:8000',
  56 + // target: 'http://localhost:18000',
  57 + // target: 'http://39.108.227.113:8000',
  58 + // target: '192.168.31.250:18000',
  59 + // target: 'http://localhost:8000',
53 60 changeOrigin: true,
54 61 ws: true,
55 62 // rewrite: (path) => {
... ...