Browse Source

利润、财务分析

caojunjie 2 years ago
parent
commit
b18652ca03

+ 5 - 3
package.json

@@ -40,9 +40,9 @@
     "axios": "^0.21.1",
     "clipboard": "2.0.6",
     "core-js": "3.8.1",
-    "echarts": "^4.9.0",
+    "echarts": "^5.4.2",
     "element-ui": "2.15.0",
-    "file-saver": "2.0.4",
+    "file-saver": "^2.0.4",
     "fuse.js": "6.4.3",
     "highlight.js": "9.18.5",
     "js-beautify": "1.13.0",
@@ -59,7 +59,9 @@
     "vue-cropper": "0.5.5",
     "vue-router": "3.4.9",
     "vuedraggable": "2.24.3",
-    "vuex": "^3.6.2"
+    "vuex": "^3.6.2",
+    "xlsx": "^0.18.5",
+    "xlsx-style": "^0.8.13"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "4.4.6",

+ 18 - 0
src/api/costAnalysis/index.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 查询环比图表数据
+export function costLinkAnalysis(query) {
+  return request({
+    url: '/anpin/management/costLinkAnalysis',
+    method: 'get',
+    params:query
+  })
+}
+// 查询同比图表数据
+export function costYOYAnalysis(query) {
+  return request({
+    url: '/anpin/management/costYOYAnalysis',
+    method: 'get',
+    params:query
+  })
+}

+ 10 - 0
src/api/profitAnalysis.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 查询环比图表数据
+export function costLinkAnalysis(query) {
+  return request({
+    url: '/anpin/management/profitAnalysis',
+    method: 'get',
+    params:query
+  })
+}

+ 1 - 1
src/api/system/set.js

@@ -73,6 +73,6 @@ export function select(query) {
 export function deleteAttachment(fId){
   return request({
     url:`/warehouseBusiness/enclosure/${fId}`,
-    method:'post'
+    method:'delete'
   })
 }

+ 1 - 1
src/combination/listComponent.vue

@@ -12,7 +12,7 @@
           </el-upload>
           <el-button v-else style="float: left;margin-right: 10px" :type="item.type" :size="item.size" :icon="item.icon"
             :plain="item.plain ? item.plain : false" :disabled="item.disabled"
-            v-hasPermi="item.hasPermi ? item.hasPermi : hasPermi.lookup" @click="buttonList(item)">
+                     v-hasPermi="item.hasPermi ? item.hasPermi : hasPermi.lookup" @click="buttonList(item)">
             {{ item.name }}
           </el-button>
         </span>

+ 1 - 1
src/components/cUpload/index.vue

@@ -377,7 +377,7 @@ export default {
         width: 150
       }, {
         surface: '5',
-        label: 'remarks',
+        label: 'remark',
         inputType: 1,
         name: '备注',
         checked: 0

+ 2 - 1
src/main.js

@@ -20,7 +20,8 @@ import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels,
 import Pagination from "@/components/Pagination";
 // 自定义表格工具扩展
 import RightToolbar from "@/components/RightToolbar"
-import echarts from "echarts";
+// import echarts from "echarts";
+import * as echarts from 'echarts'
 Vue.prototype.$echarts = echarts;
 //自定义列表组件
 import listComponent from '@/combination/listComponent'

+ 583 - 0
src/views/costAnalysis/index.vue

@@ -0,0 +1,583 @@
+<template>
+  <div class="app-container" style="padding: 0;">
+    <el-tabs type="border-card" v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="环比分析" name="1">
+        <el-form :model="ringSearch" ref="ringSearch" label-width="100px" class="demo-ruleForm">
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="费用名称" prop="name">
+                <el-select style="width: 100%;" v-model="ringSearch.expenseIds" placeholder="请选择" multiple
+                           collapse-tags clearable
+                >
+                  <el-option
+                    v-for="item in dataList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="日期" prop="name">
+                <el-date-picker
+                  v-model="ringSearch.year"
+                  type="year"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy"
+                  placeholder="选择年"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <div class="tabSetting" style="margin: 3px 0;display: flex;justify-content: center;">
+                <el-button type="success" plain icon="el-icon-search" size="mini" @click="query()">搜索
+                </el-button>
+                <el-button type="warning" plain icon="el-icon-refresh-left"
+                           @click="ringSearch = {expenseIds:[],year:JSON.stringify(new Date().getFullYear())}"
+                           size="mini"
+                >重置
+                </el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+        <div id="analysis" style="width: 100%;height: 300px;"></div>
+        <div style="margin: 5px 0">
+          <el-button type="warning" @click="exportExcel" size="mini">导出数据</el-button>
+        </div>
+        <el-table
+          :data="tableData"
+          id="tableId"
+          size="mini"
+          style="width: 100%"
+        >
+          <el-table-column
+            prop="expenseName"
+            label="费用名称"
+            align="center"
+            width="140"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount1"
+            label="1月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount2"
+            label="2月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount3"
+            label="3月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount4"
+            label="4月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount5"
+            label="5月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount6"
+            label="6月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount7"
+            label="7月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount8"
+            label="8月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount9"
+            label="9月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount10"
+            label="10月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount11"
+            label="11月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="amount12"
+            label="12月"
+            align="center"
+            width="120"
+          >
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="同比分析" name="2">
+        <el-form :model="compareSearch" ref="compareSearch" label-width="100px" class="demo-ruleForm">
+          <el-row>
+            <el-col :span="6">
+              <el-form-item label="费用名称" prop="name">
+                <el-select style="width: 100%;" v-model="compareSearch.expenseIds" placeholder="请选择" multiple
+                           collapse-tags clearable
+                >
+                  <el-option
+                    v-for="item in dataList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="当前日期" prop="name">
+                <el-date-picker
+                  v-model="compareSearch.currentDate"
+                  type="month"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy-MM"
+                  placeholder="选择年月"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="对比日期" prop="name">
+                <el-date-picker
+                  v-model="compareSearch.comparisonDate"
+                  type="month"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy-MM"
+                  placeholder="选择年月"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <div class="tabSetting" style="margin: 3px 0;display: flex;justify-content: center;">
+                <el-button type="success" plain icon="el-icon-search" size="mini" @click="queryTow()">搜索
+                </el-button>
+                <el-button type="warning" plain icon="el-icon-refresh-left" @click="compareSearch = {
+                  expenseIds:[],
+                  currentDate: `${new Date().getFullYear()}-${(new Date().getMonth() + 1) > 9 ? (new Date().getMonth() + 1) : '0' + (new Date().getMonth() + 1)}`,
+                  comparisonDate: `${(new Date().getFullYear() - 1)}-${(new Date().getMonth() + 1) > 9 ? (new Date().getMonth() + 1) : '0' + (new Date().getMonth() + 1)}`
+                }" size="mini"
+                >重置
+                </el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+        <div id="compare" style="width: 100%;height: 300px;"></div>
+        <el-table
+          :data="tableDataTwo"
+          size="mini"
+          style="width: 100%"
+        >
+          <el-table-column
+            prop="expenseName"
+            label="费用名称"
+            align="center"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="currentAmount"
+            :label="compareSearch.currentDate"
+            align="center"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="comparisonAmount"
+            :label="compareSearch.comparisonDate"
+            align="center"
+          >
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { feesList } from '@/api/costManagement'
+import { costLinkAnalysis, costYOYAnalysis } from '@/api/costAnalysis'
+import FileSaver from 'file-saver'
+import * as XLSX from 'xlsx'
+import XLSXSTYLE from 'xlsx-style'
+
+export default {
+  name: 'index',
+  data() {
+    return {
+      activeName: '1',
+      ringSearch: {
+        expenseIds: [],
+        year: JSON.stringify(new Date().getFullYear())
+      },
+      tableData: [],
+      compareSearch: {
+        expenseIds: [],
+        currentDate: `${new Date().getFullYear()}-${(new Date().getMonth() + 1) > 9 ? (new Date().getMonth() + 1) : '0' + (new Date().getMonth() + 1)}`,
+        comparisonDate: `${(new Date().getFullYear() - 1)}-${(new Date().getMonth() + 1) > 9 ? (new Date().getMonth() + 1) : '0' + (new Date().getMonth() + 1)}`
+      },
+      tableDataTwo: [],
+      dataList: []
+    }
+  },
+  created() {
+    feesList().then(res => {
+      for (let item in res.data) {
+        this.dataList.push({
+          label: res.data[item].fName,
+          value: JSON.parse(res.data[item].fId)
+        })
+      }
+    })
+    this.query()
+  },
+  methods: {
+    // 文件流转换
+    s2ab(s) {
+      //如果存在ArrayBuffer对象(es6) 最好采用该对象
+      if (typeof ArrayBuffer !== 'undefined') {
+        //1、创建一个字节长度为s.length的内存区域
+        const buf = new ArrayBuffer(s.length)
+        //2、创建一个指向buf的Unit8视图,开始于字节0,直到缓冲区的末尾
+        const view = new Uint8Array(buf)
+        //3、返回指定位置的字符的Unicode编码
+        for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
+        return buf
+      } else {
+        const buf = new Array(s.length)
+        for (let i = 0; i !== s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff
+        return buf
+      }
+    },
+    //设置表格样式
+    setExlStyle(data) {
+      // console.log(data);
+      Object.keys(data.Sheets.Sheet1).forEach(key => {
+        //设置每个单元格属性
+        if (key.indexOf('!') < 0) {
+          // 判断的方法if (data.Sheets.Sheet1[key].t) {
+          data.Sheets.Sheet1[key].s = {
+            // 边框
+            border: {
+              top: {
+                style: 'thin'
+              },
+              bottom: {
+                style: 'thin'
+              },
+              left: {
+                style: 'thin'
+              },
+              right: {
+                style: 'thin'
+              }
+            },
+            // 字体
+
+            font: {
+              sz: 12,
+              // bold: true,
+              color: {
+                // rgb: "FFFFFF" //白色
+              }
+            },
+            // 居中
+
+            alignment: {
+              horizontal: 'center',
+              vertical: 'center',
+              wrapText: true
+            }
+            //背景颜色
+            // fill: {
+            //   fgColor: {
+            //     rgb: "808080" //灰色
+            //   }
+            // }
+          }
+          if (key.replace(/[^0-9]/ig, '') === '1') {
+            data.Sheets.Sheet1[key].s = {
+              fill: { //背景色
+                fgColor: { rgb: 'C0C0C0' }
+              },
+              font: {//字体
+                name: '宋体',
+                sz: 12,
+                bold: true,
+                color: {
+                  rgb: 'FFFFFF' //白色
+                }
+              },
+              border: {//边框
+                bottom: {
+                  style: 'thin',
+                  color: 'FF000000'
+                }
+              },
+              alignment: {
+                horizontal: 'center' //水平居中
+              }
+            }
+          }
+        }
+      })
+      //导出的excel会给k这列两行有样式,手动去掉
+      // data.Sheets.Sheet1["K1"].s = {};
+      // data.Sheets.Sheet1["K2"].s = {};
+
+      //列宽
+      // data.Sheets.Sheet1["!cols"] = [];
+      //行宽
+      // data.Sheets.Sheet1["!rows"] = [];
+      //根据table.length 行数设置每行宽度
+      // data.Sheets.Sheet1["!rows"].push({ hpx: 20 });
+
+      //根据列数设置每一列的列宽  这里table有10列
+      for (let i = 0; i < 13; i++) {
+        data.Sheets.Sheet1['!cols'].push({ wpx: 100, wch: 20 })
+      }
+      data.Sheets.Sheet1['!cols'][0].wpx = 150
+      //
+      return data
+    },
+    // 导出
+    exportExcel() {
+      //转换成excel时,使用原始的格式
+      let xlsxParam = { raw: true }
+      let fix = document.querySelector('.el-table__fixed')
+      let wb
+      //判断有无fixed定位,如果有的话去掉,后面再加上,不然数据会重复
+      if (fix) {
+        wb = XLSX.utils.table_to_book(
+          document.querySelector('#tableId').removeChild(fix), xlsxParam
+        )
+        document.querySelector('#tableId').appendChild(fix)
+      } else {
+        // wb = XLSX.utils.table_to_book(document.querySelector('#tableId'), xlsxParam)
+        wb = this.setExlStyle(
+          XLSX.utils.table_to_book(
+            document.querySelector('#tableId'),
+            xlsxParam
+          )
+        )
+      }
+      let wbout = XLSXSTYLE.write(wb, {
+        bookType: 'xlsx',
+        bookSST: false,
+        type: 'binary'
+      })
+      try {
+        // FileSaver.saveAs(
+        //   new Blob([wbout], { type: 'application/octet-stream' }),
+        //   '费用分析-环比分析.xlsx'
+        // ) //文件名
+        FileSaver.saveAs(
+          // new Blob([wbout], { type: "application/octet-stream" }),
+          new Blob([this.s2ab(wbout)], {
+            type: 'application/octet-stream'
+          }),
+          '费用分析-环比分析.xlsx'
+        )
+      } catch (e) {
+        if (typeof console !== 'undefined') console.log(e, wbout)
+      }
+      return wbout
+    },
+    query() {
+      costLinkAnalysis({
+        ...this.ringSearch,
+        expenseIds: this.ringSearch.expenseIds.join(',')
+      }).then(res => {
+        this.tableData = res.data.list
+        this.$nextTick(() => {
+          this.analysis(res.data.LinkAnalysisList)
+        })
+      })
+    },
+    queryTow() {
+      costYOYAnalysis({
+        ...this.compareSearch,
+        expenseIds: this.compareSearch.expenseIds.join(',')
+      }).then(res => {
+        this.tableDataTwo = res.data.list
+        this.$nextTick(() => {
+          this.compare(res.data.costYOYAnalysisList)
+        })
+      })
+    },
+    handleClick(tab, event) {
+      if (this.activeName === '1') {
+        this.$nextTick(() => {
+          this.query()
+        })
+      } else {
+        this.$nextTick(() => {
+          this.queryTow()
+        })
+      }
+    },
+    analysis(data = []) {
+      // 基于准备好的dom,初始化echarts实例,所以只能在mounted中调用
+      let series = []
+      if (data.length > 0) {
+        for (let i = 0; i < (data[0].length - 1); i++) {
+          series.push({ type: 'bar' })
+        }
+      }
+      let myChart = this.$echarts.getInstanceByDom(document.getElementById('analysis'))
+      //如果为空 则正常进行渲染 反之 不再进行初始化
+      if (myChart == null) {
+        myChart = this.$echarts.init(document.getElementById('analysis'))
+      } else {
+        myChart.clear()
+      }
+      myChart.setOption({
+        legend: {
+          left: 'center',
+          top: '4%'
+        },
+        tooltip: {},
+        grid: {
+          top:'4%',
+          left: '3%',
+          right: '3%',
+          bottom: '16%',
+          containLabel: true
+        },
+        dataset: {
+          source: data
+        },
+        dataZoom: [
+          {
+            show: true,
+            // start: 94,
+            // end: 100
+          },
+          {
+            type: 'inside',
+            start: 94,
+            end: 100
+          },
+          {
+            show: false,
+            yAxisIndex: 0,
+            filterMode: 'empty',
+            width: 30,
+            height: '80%',
+            showDataShadow: false,
+            left: '93%'
+          }
+        ],
+        xAxis: { type: 'category' },
+        yAxis: {},
+        series: series
+      })
+    },
+    compare(data = []) {
+      // 基于准备好的dom,初始化echarts实例,所以只能在mounted中调用
+      let series = []
+      if (data.length > 0) {
+        for (let i = 0; i < (data[0].length - 1); i++) {
+          series.push({ type: 'bar' })
+        }
+      }
+      let myChart = this.$echarts.getInstanceByDom(document.getElementById('compare'))
+      //如果为空 则正常进行渲染 反之 不再进行初始化
+      if (myChart == null) {
+        myChart = this.$echarts.init(document.getElementById('compare'))
+      } else {
+        myChart.clear()
+      }
+      myChart.setOption({
+        legend: {
+          left: 'center',
+          top: '4%'
+        },
+        tooltip: {},
+        grid: {
+          top:'4%',
+          left: '3%',
+          right: '3%',
+          bottom: '16%',
+          containLabel: true
+        },
+
+        dataZoom: [
+          {
+            show: true,
+            // start: 94,
+            // end: 100
+          },
+          {
+            type: 'inside',
+            start: 94,
+            end: 100
+          },
+          {
+            show: false,
+            yAxisIndex: 0,
+            filterMode: 'empty',
+            width: 30,
+            height: '80%',
+            showDataShadow: false,
+            left: '93%'
+          }
+        ],
+        dataset: {
+          source: data
+        },
+        xAxis: { type: 'category' },
+        yAxis: {},
+        series: series
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 97 - 53
src/views/costManagement/index.vue

@@ -125,7 +125,7 @@
       @refreshDataList="returnData"
     ></approval-comments>
     <el-dialog
-      title="打印采购"
+      title="打印报销单"
       :visible.sync="dialogVisible"
       :fullscreen="true"
       style="padding: 0;margin:0"
@@ -134,63 +134,68 @@
       <div ref="print">
         <table class="table table-striped table-bordered" align="center" valign="center">
           <tr>
-            <td style="font-size: 24px;font-weight:bold" class="column" colspan="6">采购表</td>
+            <td style="font-size: 24px;font-weight:bold" class="column" colspan="11">报销单</td>
           </tr>
           <tr>
-            <td class="column" style="width: 10%;">项目</td>
-            <td class="column" style="width: 25%;">{{ formList.projectName }}</td>
-            <td class="column" style="width: 10%;">供应商</td>
-            <td class="column" style="width: 25%;">{{ formList.corpName }}</td>
-            <td class="column" style="width: 25%;" colspan="2">日期:{{ formList.fBsdate }}</td>
+            <td class="column" style="width: 6%;">部门</td>
+            <td class="column" colspan="2" style="width: 19%;">{{formList.deptName}}</td>
+            <td class="column" style="width: 9%;">报销方式</td>
+            <td class="column" style="width: 9%;">{{formList.expenseTypeName}}</td>
+            <td class="column" style="width: 9.5%;">业务类型</td>
+            <td class="column" style="width: 9.5%;">{{formList.businessTypeName}}</td>
+            <td class="column" style="width: 9.5%;">制单人</td>
+            <td class="column" style="width: 9.5%;">{{formList.createBy}}</td>
+            <td class="column" style="width: 9.5%;">制单日期</td>
+            <td class="column" style="width: 9.5%;">{{formList.createTime?formList.createTime.slice(0,10):''}}</td>
+          </tr>
+          <tr>
+            <td class="column">备注</td>
+            <td class="column" colspan="10" style="text-align: left">{{formList.remark}}</td>
           </tr>
         </table>
         <table class="table table-striped table-bordered" align="center" valign="center">
           <tr>
-            <td class="column" style="width: 15%;border-top: none;">品名</td>
-            <td class="column" style="width: 15%;border-top: none;">单价</td>
-            <td class="column" style="width: 15%;border-top: none;">采购计划</td>
-            <td class="column" style="width: 15%;border-top: none;">实际重量</td>
-            <td class="column" style="width: 15%;border-top: none;">金额</td>
-            <td class="column" style="width: 20%;border-top: none;">备注</td>
+            <td class="column" style="border-top: none;width: 6%">序号</td>
+            <td class="column" style="border-top: none;width: 9.5%;">费用名称</td>
+            <td class="column" style="border-top: none;width: 9.5%;">金额</td>
+            <td class="column" style="border-top: none;width: 18%;">项目</td>
+            <td class="column" style="border-top: none;width: 9.5%;">所属人员</td>
+            <td class="column" style="border-top: none;width: 19%;" colspan="2">所属部门</td>
+            <td class="column" style="border-top: none;width: 9.5%;">物料</td>
+            <td class="column" style="border-top: none;width: 19%;" colspan="2">备注</td>
           </tr>
           <tr v-for="(item,index) in contentList" :key="index">
-            <td class="column">{{ item.feeName }}</td>
-            <td class="column">{{ item.fUnitprice ? Number(item.fUnitprice).toFixed(2) : item.fUnitprice }}</td>
-            <td class="column">{{ item.fPurchase }}({{ item.fFeeunitName }})</td>
-            <td class="column">{{ item.fQty }}({{ item.fFeeunitName }})</td>
-            <td class="column">{{ item.fAmount ? Number(item.fAmount).toFixed(2) : item.fAmount }}</td>
-            <td class="column">{{ item.remark }}</td>
+            <td class="column">{{ Number(index)+1 }}</td>
+            <td class="column">{{ item.expenseName }}</td>
+            <td class="column">{{ item.amount ? Number(item.amount).toFixed(2) : item.amount }}</td>
+            <td class="column">{{ item.belongsProjectName }}</td>
+            <td class="column">{{ item.personnelName }}</td>
+            <td class="column" colspan="2">{{ item.departmentName }}</td>
+            <td class="column">{{ item.matterName }}</td>
+            <td class="column" colspan="2">{{ item.remark }}</td>
           </tr>
         </table>
         <table class="table table-striped table-bordered" align="center" valign="center">
           <tr>
-            <td colspan="6" style="border-top: none;" class="column"></td>
-          </tr>
-          <tr>
-            <td class="column" style="width: 25%;">采购人签字</td>
-            <td class="column" style="width: 25%;">厨房主管签字</td>
-            <td class="column" style="width: 25%;">验收人签字</td>
-            <td class="column" style="width: 25%;">主管签字</td>
+            <td class="column" style="width: 14%;border-top: none">金额合计(小写):</td>
+            <td class="column" colspan="4" style="text-align: left;border-top: none;width: 40%">{{formList.totalAmount?formList.totalAmount.toFixed(2):''}}</td>
+            <td class="column" style="width: 14%;border-top: none">金额合计(大写):</td>
+            <td class="column" colspan="4" style="text-align: left;border-top: none;width: 40%">{{formList.totalAmount?dealBigMoney(formList.totalAmount):''}}</td>
           </tr>
           <tr>
-            <td class="column" style="width: 25%;height: 37px;"></td>
-            <td class="column" style="width: 25%;"></td>
-            <td class="column" style="width: 25%;"></td>
-            <td class="column" style="width: 25%;"></td>
-          </tr>
-          <tr>
-            <td colspan="4"
-                style="border-top: none;font-weight:bold;text-align: right;padding-right: 10%;font-size: 16px;"
-                class="column"
-            >{{ formList.fsbuName }}
-            </td>
+            <td class="column" style="width: 15%;border: none">财务:</td>
+            <td class="column" colspan="2" style="text-align: left;border: none;width: 18%"></td>
+            <td class="column" style="width: 15%;border: none">部门经理:</td>
+            <td class="column" colspan="2" style="text-align: left;border: none;width: 18%"></td>
+            <td class="column" style="width: 15%;border: none">总经理:</td>
+            <td class="column" colspan="2" style="text-align: left;border: none;width: 18%"></td>
           </tr>
         </table>
       </div>
       <span slot="footer" class="dialog-footer">
-    <el-button @click="dialogVisible = false">取 消</el-button>
-    <el-button type="primary" @click="Printing">打印</el-button>
-  </span>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="Printing">打印</el-button>
+      </span>
     </el-dialog>
   </div>
 </template>
@@ -446,8 +451,7 @@ export default {
           size: 'mini',
           icon: 'el-icon-edit',
           name: '新单',
-          disabled: false,
-          hasPermi: ['anpin:stockControl:anPingApply']
+          disabled: false
         },
         // {
         //   type:'warning',
@@ -470,8 +474,7 @@ export default {
           size: 'mini',
           icon: 'el-icon-c-scale-to-original',
           name: '复制新单',
-          disabled: false,
-          hasPermi: ['anpin:stockControl:generateReceipts']
+          disabled: false
         }
       ],
       contentButton: [
@@ -525,13 +528,15 @@ export default {
         //   icon: 'el-icon-download',
         //   name: '导出',
         //   disabled: false
-        // }, {
-        //   type: 'info',
-        //   size: 'mini',
-        //   icon: 'el-icon-edit-outline',
-        //   name: '打印',
-        //   disabled: false
-        // }, {
+        // }
+        , {
+          type: 'info',
+          size: 'mini',
+          icon: 'el-icon-edit-outline',
+          name: '打印',
+          disabled: false
+        }
+        // , {
         //   type: 'primary',
         //   size: 'mini',
         //   icon: 'el-icon-edit-outline',
@@ -798,6 +803,35 @@ export default {
     })
   },
   methods: {
+    /** 数字金额大写转换(可以处理整数,小数,负数) */
+    dealBigMoney(n){
+      let fraction = ['角', '分'];
+      let digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
+      let unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟']  ];
+      let head = n < 0? '欠': '';
+      n = Math.abs(n);
+
+      let s = '';
+
+      for (let i = 0; i < fraction.length; i++)
+      {
+        s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
+      }
+      s = s || '整';
+      n = Math.floor(n);
+
+      for (let i = 0; i < unit[0].length && n > 0; i++)
+      {
+        let p = '';
+        for (let j = 0; j < unit[1].length && n > 0; j++)
+        {
+          p = digit[n % 10] + unit[1][j] + p;
+          n = Math.floor(n / 10);
+        }
+        s = p.replace(/(零.)*零$/, '').replace(/^$/, '零')  + unit[0][i] + s;
+      }
+      return head + s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');
+    },
     change(scope) {
       console.log(this.contentList)
       for (let item in this.contentList) {
@@ -1029,7 +1063,7 @@ export default {
                 item.disabled = false
               }
             }else {
-              if (item.name == '修改') {
+              if (item.name == '修改' || item.name == '打印') {
                 item.disabled = false
               } else {
                 item.disabled = true
@@ -1556,7 +1590,7 @@ export default {
           this.contentOption.forEach(item => item.disabled = true)
           this.contentStyle.forEach(item => item.disabled = true)
           this.contentButton.forEach(item => {
-            if (item.name == '返回列表' || item.name == '修改') {
+            if (item.name == '返回列表' || item.name == '修改' || item.name == '打印') {
               item.disabled = false
             } else {
               item.disabled = true
@@ -1627,6 +1661,11 @@ export default {
         if (rows[index].fId) {
           listDelete(rows[index].fId).then(data => {
             rows.splice(index, 1)
+            let amount = 0
+            for (let item of rows){
+              amount += item.amount?Number(item.amount):0
+            }
+            this.$set(this.$refs.avatar.form, 'totalAmount', amount.toFixed(2))
             this.$message({
               type: 'success',
               message: '删除成功!'
@@ -1634,6 +1673,11 @@ export default {
           })
         } else {
           rows.splice(index, 1)
+          let amount = 0
+          for (let item of rows){
+            amount += item.amount?Number(item.amount):0
+          }
+          this.$set(this.$refs.avatar.form, 'totalAmount', amount.toFixed(2))
           this.$message({
             type: 'success',
             message: '删除成功!'

+ 545 - 0
src/views/profitAnalysis/index.vue

@@ -0,0 +1,545 @@
+<template>
+  <div class="app-container" style="padding: 0;">
+    <el-tabs type="border-card" v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="环比分析" name="1">
+        <el-form :model="ringSearch" ref="ringSearch" label-width="100px" class="demo-ruleForm">
+          <el-row>
+            <el-col :span="8">
+              <el-form-item label="项目" prop="name">
+                <el-select style="width: 100%;" v-model="ringSearch.corpId" placeholder="请选择" filterable clearable
+                >
+                  <el-option
+                    v-for="item in dataList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="日期" prop="name">
+                <el-date-picker
+                  v-model="ringSearch.year"
+                  type="year"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy"
+                  placeholder="选择年"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <div class="tabSetting" style="margin: 3px 0;display: flex;justify-content: center;">
+                <el-button type="success" plain icon="el-icon-search" size="mini" @click="query()">搜索
+                </el-button>
+                <el-button type="warning" plain icon="el-icon-refresh-left"
+                           @click="ringSearch = {expenseIds:[],year:JSON.stringify(new Date().getFullYear())}"
+                           size="mini"
+                >重置
+                </el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+        <div id="analysis" style="width: 100%;height: 300px;"></div>
+        <div style="margin: 5px 0">
+          <el-button type="warning" @click="exportExcel" size="mini">导出数据</el-button>
+        </div>
+        <el-table
+          :data="tableData"
+          size="mini"
+          id="tableId"
+          style="width: 100%"
+        >
+          <el-table-column
+            prop="fname"
+            label="项目名称"
+            align="center"
+            :show-overflow-tooltip="true"
+            width="140"
+          />
+          <el-table-column
+            prop="numberpPople"
+            label="人数"
+            align="center"
+            width="120"
+          />
+          <el-table-column align="center" v-for="(item,index) in 12" :key="index"
+                           :label="`${ringSearch.year}年${item}月`"
+          >
+            <el-table-column
+              :prop="`businessIncome${item}`"
+              label="业务收入"
+              align="center"
+              width="120"
+            />
+            <el-table-column
+              :prop="`primeCost${item}`"
+              label="成本"
+              align="center"
+              width="120"
+            />
+            <el-table-column
+              :prop="`salary${item}`"
+              label="工资"
+              align="center"
+              width="120"
+            />
+            <el-table-column
+              :prop="`cost${item}`"
+              label="费用"
+              align="center"
+              width="120"
+            />
+            <el-table-column
+              :prop="`profit${item}`"
+              label="利润"
+              align="center"
+              width="120"
+            />
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="同比分析" name="2" v-if="false">
+        <el-form :model="compareSearch" ref="compareSearch" label-width="100px" class="demo-ruleForm">
+          <el-row>
+            <el-col :span="6">
+              <el-form-item label="费用名称" prop="name">
+                <el-select style="width: 100%;" v-model="compareSearch.expenseIds" placeholder="请选择" multiple
+                           collapse-tags clearable
+                >
+                  <el-option
+                    v-for="item in dataList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="当前日期" prop="name">
+                <el-date-picker
+                  v-model="compareSearch.currentDate"
+                  type="month"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy-MM"
+                  placeholder="选择年月"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="对比日期" prop="name">
+                <el-date-picker
+                  v-model="compareSearch.comparisonDate"
+                  type="month"
+                  style="width: 100%;"
+                  :clearable="false"
+                  value-format="yyyy-MM"
+                  placeholder="选择年月"
+                >
+                </el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <div class="tabSetting" style="margin: 3px 0;display: flex;justify-content: center;">
+                <el-button type="success" plain icon="el-icon-search" size="mini" @click="queryTow()">搜索
+                </el-button>
+                <el-button type="warning" plain icon="el-icon-refresh-left" @click="compareSearch = {
+                  expenseIds:[],
+                  currentDate:`${new Date().getFullYear()}-${(new Date().getMonth()+1)>9?(new Date().getMonth()+1):'0'+(new Date().getMonth()+1)}`,
+                  comparisonDate:`${new Date().getFullYear()}-${new Date().getMonth()>9?new Date().getMonth():'0'+new Date().getMonth()}`
+                }" size="mini"
+                >重置
+                </el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table
+          :data="tableDataTwo"
+          size="mini"
+          style="width: 100%"
+        >
+          <el-table-column
+            prop="expenseName"
+            label="费用名称"
+            align="center"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="currentAmount"
+            :label="compareSearch.currentDate"
+            align="center"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="comparisonAmount"
+            :label="compareSearch.comparisonDate"
+            align="center"
+          >
+          </el-table-column>
+        </el-table>
+        <div id="compare" style="width: 100%;height: 300px;"></div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { company } from '@/api/costManagement'
+import { costLinkAnalysis } from '@/api/profitAnalysis'
+import FileSaver from 'file-saver'
+import * as XLSX from 'xlsx'
+import XLSXSTYLE from 'xlsx-style'
+
+export default {
+  name: 'index',
+  data() {
+    return {
+      activeName: '1',
+      ringSearch: {
+        year: JSON.stringify(new Date().getFullYear())
+      },
+      tableData: [],
+      compareSearch: {
+        currentDate: `${new Date().getFullYear()}-${(new Date().getMonth() + 1) > 9 ? (new Date().getMonth() + 1) : '0' + (new Date().getMonth() + 1)}`,
+        comparisonDate: `${new Date().getFullYear()}-${new Date().getMonth() > 9 ? new Date().getMonth() : '0' + new Date().getMonth()}`
+      },
+      tableDataTwo: [],
+      dataList: []
+    }
+  },
+  created() {
+    company(205).then(res => {
+      for (let item in res.data) {
+        this.dataList.push({
+          label: res.data[item].fName,
+          value: JSON.parse(res.data[item].fId)
+        })
+      }
+    })
+    this.query()
+  },
+  methods: {
+    query() {
+      costLinkAnalysis({
+        ...this.ringSearch
+      }).then(res => {
+        this.tableData = res.data.list
+        this.$nextTick(() => {
+          this.analysis(res.data.profitAnalysisList)
+        })
+      })
+    },
+    queryTow() {
+      costYOYAnalysis({
+        ...this.compareSearch,
+        expenseIds: this.compareSearch.expenseIds.join(',')
+      }).then(res => {
+        this.tableDataTwo = res.data.list
+        this.$nextTick(() => {
+          this.compare(res.data.costYOYAnalysisList)
+        })
+      })
+    },
+    handleClick(tab, event) {
+      if (this.activeName === '1') {
+        this.$nextTick(() => {
+          this.query()
+        })
+      } else {
+        this.$nextTick(() => {
+          this.queryTow()
+        })
+      }
+    },
+    analysis(data = []) {
+      // 基于准备好的dom,初始化echarts实例,所以只能在mounted中调用
+      let series = []
+      if (data.length > 0) {
+        for (let i = 0; i < (data[0].length - 1); i++) {
+          series.push({ type: 'bar' })
+        }
+      }
+      let myChart = this.$echarts.getInstanceByDom(document.getElementById('analysis'))
+      //如果为空 则正常进行渲染 反之 不再进行初始化
+      if (myChart == null) {
+        myChart = this.$echarts.init(document.getElementById('analysis'))
+      }
+      myChart.clear()
+      myChart.setOption({
+        legend: {
+          left: 'center',
+          top: '4%',
+          show: false
+        },
+        tooltip: {},
+        grid: {
+          top:'4%',
+          left: '3%',
+          right: '3%',
+          bottom: '16%',
+          containLabel: true
+        },
+        dataset: {
+          source: data
+        },
+        xAxis: { type: 'category' },
+        yAxis: {},
+        dataZoom: [
+          // {
+          //   show: true,
+          //   // start: 94,
+          //   // end: 100
+          // },
+          {
+            type: 'slider'
+          },
+          {
+            type: 'inside'
+          },
+          {
+            show: false,
+            yAxisIndex: 0,
+            filterMode: 'empty',
+            width: 30,
+            height: '80%',
+            showDataShadow: false,
+            left: '93%'
+          }
+        ],
+        series: series
+      })
+    },
+    compare(data = []) {
+      // 基于准备好的dom,初始化echarts实例,所以只能在mounted中调用
+      let series = []
+      if (data.length > 0) {
+        for (let i = 0; i < (data[0].length - 1); i++) {
+          series.push({ type: 'bar' })
+        }
+      }
+      let myChart = this.$echarts.getInstanceByDom(document.getElementById('compare'))
+      //如果为空 则正常进行渲染 反之 不再进行初始化
+      if (myChart == null) {
+        myChart = this.$echarts.init(document.getElementById('compare'))
+      }
+      myChart.clear()
+      myChart.setOption({
+        legend: {
+          left: 'center',
+          top: '4%'
+        },
+        tooltip: {},
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
+        dataset: {
+          source: data
+        },
+        xAxis: { type: 'category' },
+        yAxis: {},
+        series: series
+      })
+    },
+
+    // 文件流转换
+    s2ab(s) {
+      //如果存在ArrayBuffer对象(es6) 最好采用该对象
+      if (typeof ArrayBuffer !== 'undefined') {
+        //1、创建一个字节长度为s.length的内存区域
+        const buf = new ArrayBuffer(s.length)
+        //2、创建一个指向buf的Unit8视图,开始于字节0,直到缓冲区的末尾
+        const view = new Uint8Array(buf)
+        //3、返回指定位置的字符的Unicode编码
+        for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
+        return buf
+      } else {
+        const buf = new Array(s.length)
+        for (let i = 0; i !== s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff
+        return buf
+      }
+    },
+    //设置表格样式
+    setExlStyle(data) {
+      // console.log(data);
+      Object.keys(data.Sheets.Sheet1).forEach(key => {
+        //设置每个单元格属性
+        if (key.indexOf('!') < 0) {
+          // 判断的方法if (data.Sheets.Sheet1[key].t) {
+          data.Sheets.Sheet1[key].s = {
+            // 边框
+            border: {
+              top: {
+                style: 'thin'
+              },
+              bottom: {
+                style: 'thin'
+              },
+              left: {
+                style: 'thin'
+              },
+              right: {
+                style: 'thin'
+              }
+            },
+            // 字体
+
+            font: {
+              sz: 12,
+              // bold: true,
+              color: {
+                // rgb: "FFFFFF" //白色
+              }
+            },
+            // 居中
+
+            alignment: {
+              horizontal: 'center',
+              vertical: 'center',
+              wrapText: true
+            }
+            //背景颜色
+            // fill: {
+            //   fgColor: {
+            //     rgb: "808080" //灰色
+            //   }
+            // }
+          }
+          if (key.replace(/[^0-9]/ig, '') === '1') {
+            data.Sheets.Sheet1[key].s = {
+              fill: { //背景色
+                fgColor: { rgb: 'f6f7fa' }
+              },
+              font: {//字体
+                name: '宋体',
+                sz: 12,
+                bold: true,
+                color: {
+                  rgb: '000000' //黑色
+                }
+              },
+              border: {//边框
+                bottom: {
+                  style: 'thin',
+                  color: 'FF000000'
+                }
+              },
+              alignment: {
+                horizontal: 'center' //水平居中
+              }
+            }
+          }
+          if (key.replace(/[^0-9]/ig, '') === '2') {
+            data.Sheets.Sheet1[key].s = {
+              fill: { //背景色
+                fgColor: { rgb: 'f6f7fa' }
+              },
+              font: {//字体
+                name: '宋体',
+                sz: 12,
+                bold: true,
+                color: {
+                  rgb: '000000' //黑色
+                }
+              },
+              border: {//边框
+                top: {
+                  style: 'thin'
+                },
+                bottom: {
+                  style: 'thin'
+                },
+                left: {
+                  style: 'thin'
+                },
+                right: {
+                  style: 'thin'
+                }
+              },
+              alignment: {
+                horizontal: 'center' //水平居中
+              }
+            }
+          }
+        }
+      })
+      //导出的excel会给k这列两行有样式,手动去掉
+      data.Sheets.Sheet1['BK1'].s = {}
+      data.Sheets.Sheet1['BK2'].s = {}
+
+      //列宽
+      // data.Sheets.Sheet1["!cols"] = [];
+      //行宽
+      // data.Sheets.Sheet1["!rows"] = [];
+      //根据table.length 行数设置每行宽度
+      // data.Sheets.Sheet1["!rows"].push({ hpx: 20 });
+
+      //根据列数设置每一列的列宽  这里table有10列
+      for (let i = 0; i < 62; i++) {
+        data.Sheets.Sheet1['!cols'].push({ wpx: 100, wch: 20 })
+      }
+      data.Sheets.Sheet1['!cols'][0].wpx = 200
+      //
+      return data
+    },
+    // 导出
+    exportExcel() {
+      //转换成excel时,使用原始的格式
+      let xlsxParam = { raw: true }
+      let fix = document.querySelector('.el-table__fixed')
+      let wb
+      //判断有无fixed定位,如果有的话去掉,后面再加上,不然数据会重复
+      if (fix) {
+        wb = XLSX.utils.table_to_book(
+          document.querySelector('#tableId').removeChild(fix), xlsxParam
+        )
+        document.querySelector('#tableId').appendChild(fix)
+      } else {
+        // wb = XLSX.utils.table_to_book(document.querySelector('#tableId'), xlsxParam)
+        wb = this.setExlStyle(
+          XLSX.utils.table_to_book(
+            document.querySelector('#tableId'),
+            xlsxParam
+          )
+        )
+      }
+      let wbout = XLSXSTYLE.write(wb, {
+        bookType: 'xlsx',
+        bookSST: false,
+        type: 'binary'
+      })
+      try {
+        // FileSaver.saveAs(
+        //   new Blob([wbout], { type: 'application/octet-stream' }),
+        //   '费用分析-环比分析.xlsx'
+        // ) //文件名
+        FileSaver.saveAs(
+          // new Blob([wbout], { type: "application/octet-stream" }),
+          new Blob([this.s2ab(wbout)], {
+            type: 'application/octet-stream'
+          }),
+          '费用分析-环比分析.xlsx'
+        )
+      } catch (e) {
+        if (typeof console !== 'undefined') console.log(e, wbout)
+      }
+      return wbout
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 3 - 3
vue.config.js

@@ -34,9 +34,9 @@ module.exports = {
     proxy: {
       // detail: https://cli.vuejs.org/config/#devserver-proxy
       [process.env.VUE_APP_BASE_API]: {
-        // target: `http://192.168.0.106:9020`,
+        target: `http://192.168.0.106:9020`,
         // target: `http://162.14.112.182:9020/`,
-        target: `https://ap.tubaosoft.com/prod-api/`,
+        // target: `https://ap.tubaosoft.com/prod-api/`,
         changeOrigin: true,
         pathRewrite: {
           ['^' + process.env.VUE_APP_BASE_API]: ''
@@ -56,7 +56,7 @@ module.exports = {
   chainWebpack(config) {
     config.plugins.delete('preload') // TODO: need test
     config.plugins.delete('prefetch') // TODO: need test
-
+    config.externals({ './cptable': 'var cptable' })
     // set svg-sprite-loader
     config.module
       .rule('svg')