salesfunnel.vue 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. // 各模块文件存储占比
  2. <template>
  3. <div class="container normal-panel">
  4. <div class="inline-16" style="margin-top:0px;margin-bottom: 20px">
  5. <label class="search__label" >{{$t('部门') +':'}}</label>
  6. <el-cascader ref="selectdep" size="small" v-model="depment" :options="deplist" :props="{emitPath:true,expandTrigger:'hover',checkStrictly:true,label:'label',value:'departmentid',children:'children'}" @change="selectDep" clearable></el-cascader>
  7. </div>
  8. <div class="inline-16">
  9. <label class="search__label" >{{$t('业务员' +':')}}</label>
  10. <el-select v-model="person" filterable :placeholder="$t('请选择')" size="small" clearable @change="selectPerson">
  11. <el-option
  12. v-for="item in personnelList"
  13. :key="item.index"
  14. :label="$t(item.name)"
  15. :value="item.userid">
  16. </el-option>
  17. </el-select>
  18. </div>
  19. <div class="inline-16">
  20. <p class="search__label">{{$t('状态') +':'}}</p>
  21. <el-select v-model="isleave" clearable style="margin-right:10px" size="small" :placeholder="$t('请选择状态')" @change="leaveChange" >
  22. <el-option :label="$t('在职')" value="1"></el-option>
  23. <el-option :label="$t('离职')" value="2"></el-option>
  24. </el-select>
  25. </div>
  26. <div class="inline-16">
  27. <p class="search__label">{{$t('领域') +':'}}</p>
  28. <el-select v-model="tradefield" clearable style="margin-right:10px" size="small" :placeholder="$t('请选择领域')" @change="dataParam.content.where.tradefield = tradefield;projectParam.content.where.tradefield = tradefield;getProportionOfFileModel()">
  29. <el-option v-for="item in tradefields" :label="$t(item.value)" :key="item.rowindex" :value="item.value">
  30. </el-option>
  31. </el-select>
  32. </div>
  33. <div class="inline-16">
  34. <el-checkbox v-model="unfinish" true-label="0" false-label="1" @change="dataParam.content.where.unfinish = unfinish;projectParam.content.where.unfinish = unfinish;getProportionOfFileModel()">{{$t(`包含失败、结案项目`)}}</el-checkbox>
  35. </div>
  36. <div class="inline-16" style="margin-top:0px;margin-bottom: 20px">
  37. <el-button-group>
  38. <el-button size="small" :type="dataParam.content.dateType==99?'primary':''" @click="dataChange(99)">{{$t(`全部`)}}</el-button>
  39. <el-button size="small" :type="dataParam.content.dateType==1?'primary':''" @click="dataChange(1)">{{$t(`近一年`)}}</el-button>
  40. <el-button size="small" :type="dataParam.content.dateType==2?'primary':''" @click="dataChange(2)">{{$t(`近九个月`)}}</el-button>
  41. <el-button size="small" :type="dataParam.content.dateType==3?'primary':''" @click="dataChange(3)">{{$t(`近六个月`)}}</el-button>
  42. <el-button size="small" :type="dataParam.content.dateType==4?'primary':''" @click="dataChange(4)">{{$t(`近三个月`)}}</el-button>
  43. </el-button-group>
  44. </div>
  45. <div class="inline-16" style="margin-top:0px;margin-bottom: 20px">
  46. <el-date-picker
  47. size="small"
  48. v-model="dateSelect"
  49. @change="dateChange"
  50. type="daterange"
  51. :clearable="false"
  52. format="yyyy-MM-dd"
  53. value-format="yyyy-MM-dd"
  54. :range-separator="$t('至')"
  55. :start-placeholder="$t('开始日期')"
  56. :end-placeholder="$t('结束日期')">
  57. </el-date-picker>
  58. </div>
  59. <div>
  60. <el-row>
  61. <el-col :xs="15" :sm="15" :md="15" :lg="15" :xl="14">
  62. <p class="title">{{$t(`销售漏斗图`)}}</p>
  63. <div class="re-panel">
  64. <div id="containerFunnel" style="height: calc(60vh)"></div>
  65. </div>
  66. </el-col>
  67. <el-col :offset="1" :xs="8" :sm="8" :md="8" :lg="8" :xl="9">
  68. <div>
  69. <p class="title">{{$t(`表格数据`)}}</p>
  70. <tableNewTemp :layout="tablecolsData" :data="tableData" :opwidth="200" :custom="true" :headerOptions="['signamount_due','dealamount']">
  71. <template v-slot:header="scope">
  72. <div v-if="scope.column.columnname == 'signamount_due'">
  73. <p>{{$t(`预计签约`)}}</p>
  74. <p>{{$t(`金额(万元)`)}}</p>
  75. </div>
  76. <div v-if="scope.column.columnname == 'dealamount'">
  77. <p>{{$t(`项目成交`)}}</p>
  78. <p>{{$t(`金额(万元)`)}}</p>
  79. </div>
  80. </template>
  81. <template v-slot:customcol="scope">
  82. <div>
  83. {{scope.column.data[[scope.column.columnname]]?scope.column.data[[scope.column.columnname]]:'--'}}
  84. </div>
  85. </template>
  86. </tableNewTemp>
  87. </div>
  88. </el-col>
  89. </el-row>
  90. </div>
  91. <div v-if="siteid == 'HY' || siteid == 'YOSTEST1'">
  92. <p class="title">{{$t(`项目预计成交分析`)}}</p>
  93. <previousTwelveMonths :data="previousData"></previousTwelveMonths>
  94. <futureTwelveMonths ref="futureTwelveMonthsRef" style="margin-top: 15px"></futureTwelveMonths>
  95. </div>
  96. <div style="margin-top: 40px">
  97. <p class="title">{{projectTile}}</p>
  98. <tableTemp :layout="tablecols" :data="projectList" :opwidth="200" :custom="true" :height="tableHieght">
  99. <template v-slot:customcol="scope">
  100. <div v-if="scope.column.columnname === 'status'">
  101. <span :style="{color:scope.column.data[[scope.column.columnname]] == '跟进中'?'#52c41a':tool.getStatusColor(scope.column.data[[scope.column.columnname]],true)}" >{{$t(scope.column.data[[scope.column.columnname]])}}</span>
  102. </div>
  103. <div v-else-if="scope.column.columnname === 'tag_sys'">
  104. <div v-for="item in scope.column.data.tag_sys" :key="item.index" style="float: left;margin-left: 5px;margin-bottom: 5px">
  105. <el-tag color="#3874F6" size="mini" type="primary" effect="dark">
  106. <span>{{$t(item)}}</span>
  107. </el-tag>
  108. </div>
  109. <div v-for="item in scope.column.data.tag" :key="item.index" style="float: left;margin-left: 5px;margin-bottom: 5px">
  110. <el-tag color="#FA8C16" size="mini" type="warning" effect="dark">
  111. <span>{{$t(item)}}</span>
  112. </el-tag>
  113. </div>
  114. </div>
  115. <div v-else-if="scope.column.columnname === 'leader'">
  116. {{scope.data.column.leader[0] && scope.data.column.data.leader[0].name}}
  117. </div>
  118. <div v-else-if="scope.column.columnname === 'projecttype'">
  119. {{scope.column.data.projecttype + '-' + scope.column.data.projecttype_remarks}}
  120. </div>
  121. <div v-else-if="scope.column.columnname == 'totalinvestment'">
  122. <span>{{scope.column.data[[scope.column.columnname]] ?tool.formatAmount(scope.column.data[[scope.column.columnname]],2):'--'}}</span>
  123. </div>
  124. <div v-else-if="scope.column.columnname == 'costofconstruction'">
  125. <span>{{scope.column.data[[scope.column.columnname]] ?tool.formatAmount(scope.column.data[[scope.column.columnname]],2):'--'}}</span>
  126. </div>
  127. <div v-else-if="scope.column.columnname == 'budgetary'">
  128. <span>{{scope.column.data[[scope.column.columnname]] ?tool.formatAmount(scope.column.data[[scope.column.columnname]],2):'--'}}</span>
  129. </div>
  130. <div v-else-if="scope.column.columnname == 'signamount_due'">
  131. <span>{{scope.column.data[[scope.column.columnname]] ?tool.formatAmount(scope.column.data[[scope.column.columnname]],2):'--'}}</span>
  132. </div>
  133. <div v-else-if="scope.column.columnname == 'dealamount'">
  134. <span>{{scope.column.data[[scope.column.columnname]] ?tool.formatAmount(scope.column.data[[scope.column.columnname]],2):'--'}}</span>
  135. </div>
  136. <div v-else-if="scope.column.columnname == 'begdate_due'">
  137. <span>{{scope.column.data[[scope.column.columnname]] ? scope.column.data[[scope.column.columnname]] !== 'NaN-NaN'?scope.column.data[[scope.column.columnname]]:'--' :'--'}}</span>
  138. </div>
  139. <div v-else-if="scope.column.columnname == 'enddate_due'">
  140. <span>{{scope.column.data[[scope.column.columnname]] ? scope.column.data[[scope.column.columnname]] !== 'NaN-NaN'?scope.column.data[[scope.column.columnname]]:'--' :'--'}}</span>
  141. </div>
  142. <div v-else-if="scope.column.columnname == 'scale'">
  143. <span>{{scope.column.data[[scope.column.columnname]]?scope.column.data[[scope.column.columnname]] + scope.column.data.unitname:'--'}}</span>
  144. </div>
  145. <div v-else>
  146. {{scope.column.data[[scope.column.columnname]]?scope.column.data[[scope.column.columnname]]:'--'}}
  147. </div>
  148. </template>
  149. </tableTemp>
  150. <div style="text-align:right;margin-top: 10px">
  151. <el-pagination
  152. background
  153. @size-change="handleSizeChange"
  154. @current-change="handleCurrentChange"
  155. :current-page="projectParam.content.pageNumber"
  156. :page-sizes="[20,50,100,150]"
  157. :page-size="20"
  158. layout="total,sizes, prev, pager, next, jumper"
  159. :total="total">
  160. </el-pagination>
  161. </div>
  162. </div>
  163. </div>
  164. </template>
  165. <script>
  166. import tableTemp from '@/components/table/index8'
  167. import tableNewTemp from '@/components/table/index10'
  168. import { Funnel,G2 } from '@antv/g2plot';
  169. const G = G2.getEngine('canvas');
  170. import chartTemplate from '@/template/chartG2Template/Column'
  171. import previousTwelveMonths from './previousTwelveMonths'
  172. import futureTwelveMonths from './futureTwelveMonths'
  173. export default {
  174. components:{tableTemp,tableNewTemp,chartTemplate,previousTwelveMonths,futureTwelveMonths},
  175. data () {
  176. return {
  177. chartPie:null,
  178. flagIndex:'',
  179. /*tableHieght:'calc(100vh)',*/
  180. tableHieght:'857px',
  181. tableData:[],
  182. person:'',
  183. depment:'',
  184. dateType:"",
  185. projectTile:"",
  186. fullscreenLoading:false,
  187. total:0,
  188. sa_projstagemagid:'',
  189. data:[{ stagename: '简历筛选', sequence1: 253 },],
  190. activeName: '部门',
  191. dataid:'',
  192. range:'',
  193. pointValue:'',
  194. isDep:false,
  195. isPerson:false,
  196. visible:false,
  197. deplist:[],
  198. personnelList:[],
  199. projectList:[],
  200. tablecols:[],
  201. tablecolsData:[],
  202. depmentParam:{
  203. "id": 20230620102004,
  204. "content": {
  205. "isleave":'1'
  206. }
  207. },
  208. dataParam:{
  209. "id": 20230630151504,
  210. "content": {
  211. "type":0, // 0 按人搜素 1 按部门搜索
  212. "dataid":0, // 人员id或部门id
  213. 'dateType':99,
  214. "where": {
  215. "begindate": "",
  216. "begdate":"",
  217. "enddate":"",
  218. "departmentid":"",
  219. "tradefield":"",
  220. "isleave":"1",
  221. "unfinish":'1'
  222. }
  223. }
  224. },
  225. projectParam:{
  226. "id": 20230719085004,
  227. "content": {
  228. "pageNumber": 1,
  229. "pageSize": 20,
  230. "type": '',
  231. "dataid": '',
  232. "dateType": 99,
  233. "sa_projstagemagid":'',
  234. "where": {
  235. "begdate":"",
  236. "enddate":"",
  237. "tradefield":"",
  238. "isleave":"1",
  239. "unfinish":'1'
  240. }
  241. }
  242. },
  243. dateSelect:[],
  244. tradefield:'',
  245. isleave:'1',
  246. tradefields:[],
  247. unfinish:'1',
  248. siteid:JSON.parse(sessionStorage.getItem('active_account')).siteid,
  249. previousData:[],
  250. futreData:[]
  251. }
  252. },
  253. methods:{
  254. async departmentrtment() {
  255. const res = await this.$api.requested(this.depmentParam)
  256. this.deplist = this.createMenu(res.data.dep)
  257. this.personnelList = res.data.hr
  258. this.person = JSON.parse(window.sessionStorage.getItem('active_account')).name
  259. this.getProportion()
  260. },
  261. async getProportion () {
  262. const res = await this.$api.requested(this.dataParam)
  263. this.tableData = res.data
  264. sessionStorage.setItem('flagIndex',res.data.length)
  265. if (this.siteid == 'HY' || this.siteid == 'YOSTEST1'){
  266. this.expectedTransaction(true)
  267. }
  268. this.renderPie()
  269. },
  270. async personData(){
  271. const res = await this.$api.requested(this.depmentParam)
  272. this.personnelList = res.data.hr
  273. },
  274. leaveChange(){
  275. this.person = ''
  276. if (this.isleave){
  277. this.dataParam.content.where.isleave = this.isleave
  278. this.dataParam.content.dataid = this.dataParam.content.type == 0?-1:this.dataParam.content.dataid
  279. this.projectParam.content.where.isleave = this.isleave
  280. this.projectParam.content.dataid = this.projectParam.content.type == 0?-1:this.projectParam.content.dataid
  281. this.depmentParam.content.isleave = this.isleave
  282. this.personData()
  283. this.getProportionOfFileModel()
  284. }else {
  285. this.dataParam.content.where.isleave = 0
  286. this.dataParam.content.dataid = this.dataParam.content.type == 0?-1:this.dataParam.content.dataid
  287. this.projectParam.content.where.isleave = 0
  288. this.projectParam.content.dataid = this.projectParam.content.type == 0?-1:this.projectParam.content.dataid
  289. this.depmentParam.content.isleave = 0
  290. this.personData()
  291. this.getProportionOfFileModel()
  292. }
  293. },
  294. createMenu (array) {
  295. var that = this
  296. let arr = []
  297. function convertToElementTree(node) {
  298. // 新节点
  299. if (node.subdep.length === 0){
  300. var elNode = {
  301. label: node["depname"],
  302. parentid:node['parentid'],
  303. parentname:node['parentname'],
  304. departmentid:node["departmentid"],
  305. value:node["departmentid"],
  306. remarks:node["remarks"],
  307. isused:node["isused"],
  308. changedate:node['changedate'],
  309. changeby:node['changeby'],
  310. createdate:node['createdate'],
  311. createby:node['createby'],
  312. depno:node['depno'],
  313. disabled:that.pageOnlyRead,
  314. }
  315. }else {
  316. var elNode = {
  317. label: node["depname"],
  318. parentid:node['parentid'],
  319. parentname:node['parentname'],
  320. departmentid:node["departmentid"],
  321. value:node["departmentid"],
  322. remarks:node["remarks"],
  323. isused:node["isused"],
  324. changedate:node['changedate'],
  325. changeby:node['changeby'],
  326. createdate:node['createdate'],
  327. createby:node['createby'],
  328. depno:node['depno'],
  329. disabled:that.pageOnlyRead,
  330. children: []
  331. }
  332. }
  333. if (node.subdep && node.subdep.length > 0) {
  334. // 如果存在子节点
  335. for (var index = 0; index < node.subdep.length; index++) {
  336. // 遍历子节点, 把每个子节点看做一颗独立的树, 传入递归构造子树, 并把结果放回到新node的children中
  337. elNode.children.push(convertToElementTree(node.subdep[index]));
  338. }
  339. }
  340. return elNode;
  341. }
  342. array.forEach((element) => {
  343. arr.push(convertToElementTree(element))
  344. });
  345. return arr
  346. },
  347. selectDep(val) {
  348. this.person = ''
  349. this.dataParam.content.type = 1
  350. this.dataParam.content.dataid = val[val.length -1]
  351. this.getProportionOfFileModel()
  352. },
  353. selectPerson(val){
  354. this.depment = ''
  355. this.dataParam.content.type = 0
  356. this.dataParam.content.dataid = val
  357. this.getProportionOfFileModel()
  358. },
  359. dataChange(val){
  360. this.dataParam.content.dateType = val
  361. this.projectParam.content.dateType = val
  362. if (val == '1'){
  363. let currentDate = new Date(); // 获取当前日期
  364. let startDate = new Date(currentDate.getFullYear() - 1, currentDate.getMonth(), currentDate.getDate() + 1); // 计算起始日期
  365. let endDate = currentDate; // 结束日期为当前日期
  366. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  367. }else if (val == '2'){
  368. let currentDate = new Date(); // 获取当前日期
  369. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 9, currentDate.getDate() + 1); // 计算起始日期
  370. let endDate = currentDate; // 结束日期为当前日期
  371. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  372. }else if (val == '3'){
  373. let currentDate = new Date(); // 获取当前日期
  374. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 6, currentDate.getDate() + 1); // 计算起始日期
  375. let endDate = currentDate; // 结束日期为当前日期
  376. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  377. }else if (val == '4'){
  378. let currentDate = new Date(); // 获取当前日期
  379. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 3, currentDate.getDate() + 1); // 计算起始日期
  380. let endDate = currentDate; // 结束日期为当前日期
  381. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  382. }else if (val == '99'){
  383. this.dateSelect = []
  384. }
  385. this.getProportionOfFileModel()
  386. },
  387. dateSet(val){
  388. if (val == '1'){
  389. let currentDate = new Date(); // 获取当前日期
  390. let startDate = new Date(currentDate.getFullYear() - 1, currentDate.getMonth(), currentDate.getDate() + 1); // 计算起始日期
  391. let endDate = currentDate; // 结束日期为当前日期
  392. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  393. }else if (val == '2'){
  394. let currentDate = new Date(); // 获取当前日期
  395. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 9, currentDate.getDate() + 1); // 计算起始日期
  396. let endDate = currentDate; // 结束日期为当前日期
  397. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  398. }else if (val == '3'){
  399. let currentDate = new Date(); // 获取当前日期
  400. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 6, currentDate.getDate() + 1); // 计算起始日期
  401. let endDate = currentDate; // 结束日期为当前日期
  402. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  403. }else if (val == '4'){
  404. let currentDate = new Date(); // 获取当前日期
  405. let startDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 3, currentDate.getDate() + 1); // 计算起始日期
  406. let endDate = currentDate; // 结束日期为当前日期
  407. this.dateSelect = [startDate.toISOString().split('T')[0],endDate.toISOString().split('T')[0]]
  408. }else if (val == '99'){
  409. this.dateSelect = []
  410. }
  411. },
  412. dateChange(){
  413. this.dataParam.content.dateType = 0
  414. this.dataParam.content.where.begdate = this.dateSelect[0]
  415. this.dataParam.content.where.enddate = this.dateSelect[1]
  416. this.projectParam.content.dateType = 0
  417. this.projectParam.content.where.begdate = this.dateSelect[0]
  418. this.projectParam.content.where.enddate = this.dateSelect[1]
  419. this.getProportionOfFileModel()
  420. },
  421. renderPie() {
  422. if (JSON.parse(sessionStorage.getItem('flagIndex')) === 7){
  423. const colorArray = ['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed'];
  424. this.chartPie = new Funnel('containerFunnel', {
  425. data: this.tableData,
  426. maxSize:0.6,
  427. xField: 'stagename',
  428. yField: 'wide',
  429. shape: 'funnel',
  430. dynamicHeight: false,
  431. legend: false,
  432. interactions: [{ type: 'element-active'}],
  433. color:['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#fff'],
  434. label: {
  435. layout:"fixedOverlap",
  436. position:'right',
  437. offsetX:40,
  438. content:(datum)=>{
  439. const group = new G.Group({});
  440. const content = ()=>{
  441. if (this.tableData[0]) {
  442. const text = `${datum.stagename} 项目总数: ${datum.sequence1} 当前项目数: ${datum.projectqty} 转化率: ${datum.zhl?Math.round((datum.zhl* 100)*100)/100 + '%':'--'} 预计签约金额: ${datum.signamount_due}万元 项目成交金额: ${datum.dealamount}万元`
  443. const lines = text.split(' ');
  444. return lines.join('\n');
  445. }
  446. };
  447. const color = ()=>{
  448. let clr = ''
  449. this.tableData.some((e,index) =>{
  450. if (e.stagename == datum.stagename) {
  451. clr = colorArray[index]
  452. }
  453. })
  454. if (clr == '' || clr == undefined) {
  455. clr= '#ffffff'
  456. }
  457. return clr
  458. };
  459. group.addShape({
  460. type: 'text',
  461. attrs: {
  462. x: 40,
  463. y: 0,
  464. text: content(),
  465. textAlign: 'left',
  466. fontSize: 14,
  467. textBaseline: 'top',
  468. fill: color()
  469. },
  470. });
  471. return group;
  472. },
  473. },
  474. tooltip:{
  475. customContent: (title, items) => {
  476. // 构建自定义内容
  477. const content = `<div>
  478. <ul style="padding:10px;">
  479. ${items.map((item) => `
  480. <li>
  481. <p style="margin-bottom:10px">${title}</p>
  482. <p>项目总数:${item.data.projectqty}</p>
  483. </li>`).join('')}
  484. </ul>
  485. </div>`;
  486. return content;
  487. },
  488. },
  489. conversionTag: false,
  490. funnelStyle: {
  491. stroke: '#fff',
  492. lineWidth: 3,
  493. },
  494. });
  495. this.chartPie.render();
  496. document.addEventListener('click',(evt) => {
  497. const states = this.chartPie.getStates();
  498. let dataList = []
  499. dataList = states
  500. if (dataList.length > 0){
  501. this.projectTile = dataList[0].data.stagename
  502. this.sa_projstagemagid = dataList[0].data.sa_projstagemagid
  503. this.projectParam.content.pageNumber = 1
  504. this.projectParam.content.pageSize = 20
  505. this.getProjectList()
  506. }
  507. })
  508. this.getProportionOfFileModel()
  509. }else if(JSON.parse(sessionStorage.getItem('flagIndex')) === 8){
  510. const colorArray = ['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#6FD26C'];
  511. this.chartPie = new Funnel('containerFunnel', {
  512. data: this.tableData,
  513. maxSize:0.6,
  514. xField: 'stagename',
  515. yField: 'wide',
  516. shape: 'funnel',
  517. dynamicHeight: false,
  518. legend: false,
  519. interactions: [{ type: 'element-active'}],
  520. color:['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#6FD26C','#fff'],
  521. label: {
  522. layout:"fixedOverlap",
  523. position:'right',
  524. offsetX:40,
  525. content:(datum)=>{
  526. const group = new G.Group({});
  527. const content = ()=>{
  528. if (this.tableData[0]) {
  529. const text = `${datum.stagename} 项目总数: ${datum.sequence1} 当前项目数: ${datum.projectqty} 转化率: ${datum.zhl?Math.round((datum.zhl* 100)*100)/100 + '%':'--'} 预计签约金额: ${datum.signamount_due}万元 项目成交金额: ${datum.dealamount}万元`
  530. const lines = text.split(' ');
  531. return lines.join('\n');
  532. }
  533. };
  534. const color = ()=>{
  535. let clr = ''
  536. this.tableData.some((e,index) =>{
  537. if (e.stagename == datum.stagename) {
  538. clr = colorArray[index]
  539. }
  540. })
  541. if (clr == '' || clr == undefined) {
  542. clr= '#ffffff'
  543. }
  544. return clr
  545. };
  546. group.addShape({
  547. type: 'text',
  548. attrs: {
  549. x: 40,
  550. y: 0,
  551. text: content(),
  552. textAlign: 'left',
  553. fontSize: 14,
  554. textBaseline: 'top',
  555. fill: color()
  556. },
  557. });
  558. return group;
  559. },
  560. },
  561. tooltip:{
  562. customContent: (title, items) => {
  563. // 构建自定义内容
  564. const content = `<div>
  565. <ul style="padding:10px;">
  566. ${items.map((item) => `
  567. <li>
  568. <p style="margin-bottom:10px">${title}</p>
  569. <p>项目总数:${item.data.projectqty}</p>
  570. </li>`).join('')}
  571. </ul>
  572. </div>`;
  573. return content;
  574. },
  575. },
  576. conversionTag: false,
  577. funnelStyle: {
  578. stroke: '#fff',
  579. lineWidth: 3,
  580. },
  581. });
  582. this.chartPie.render();
  583. document.addEventListener('click',(evt) => {
  584. const states = this.chartPie.getStates();
  585. let dataList = []
  586. dataList = states
  587. if (dataList.length > 0){
  588. this.projectTile = dataList[0].data.stagename
  589. this.sa_projstagemagid = dataList[0].data.sa_projstagemagid
  590. this.projectParam.content.pageNumber = 1
  591. this.projectParam.content.pageSize = 20
  592. this.getProjectList()
  593. }
  594. })
  595. this.getProportionOfFileModel()
  596. }else if(JSON.parse(sessionStorage.getItem('flagIndex')) === 6){
  597. const colorArray = ['#6395fa','#63daab','#657798','#f7c122','#7666fa'];
  598. this.chartPie = new Funnel('containerFunnel', {
  599. data: this.tableData,
  600. maxSize:0.6,
  601. xField: 'stagename',
  602. yField: 'wide',
  603. shape: 'funnel',
  604. dynamicHeight: false,
  605. legend: false,
  606. interactions: [{ type: 'element-active'}],
  607. color:['#6395fa','#63daab','#657798','#f7c122','#7666fa','#fff'],
  608. label: {
  609. layout:"fixedOverlap",
  610. position:'right',
  611. offsetX:40,
  612. content:(datum)=>{
  613. const group = new G.Group({});
  614. const content = ()=>{
  615. if (this.tableData[0]) {
  616. const text = `${datum.stagename} 项目总数: ${datum.sequence1} 当前项目数: ${datum.projectqty} 转化率: ${datum.zhl?Math.round((datum.zhl* 100)*100)/100 + '%':'--'} 预计签约金额: ${datum.signamount_due}万元 项目成交金额: ${datum.dealamount}万元`
  617. const lines = text.split(' ');
  618. return lines.join('\n');
  619. }
  620. };
  621. const color = ()=>{
  622. let clr = ''
  623. this.tableData.some((e,index) =>{
  624. if (e.stagename == datum.stagename) {
  625. clr = colorArray[index]
  626. }
  627. })
  628. if (clr == '' || clr == undefined) {
  629. clr= '#ffffff'
  630. }
  631. return clr
  632. };
  633. group.addShape({
  634. type: 'text',
  635. attrs: {
  636. x: 40,
  637. y: 0,
  638. text: content(),
  639. textAlign: 'left',
  640. fontSize: 14,
  641. textBaseline: 'top',
  642. fill: color()
  643. },
  644. });
  645. return group;
  646. },
  647. },
  648. tooltip:{
  649. customContent: (title, items) => {
  650. // 构建自定义内容
  651. const content = `<div>
  652. <ul style="padding:10px;">
  653. ${items.map((item) => `
  654. <li>
  655. <p style="margin-bottom:10px">${title}</p>
  656. <p>项目总数:${item.data.projectqty}</p>
  657. </li>`).join('')}
  658. </ul>
  659. </div>`;
  660. return content;
  661. },
  662. },
  663. conversionTag: false,
  664. funnelStyle: {
  665. stroke: '#fff',
  666. lineWidth: 3,
  667. },
  668. });
  669. this.chartPie.render();
  670. document.addEventListener('click',(evt) => {
  671. const states = this.chartPie.getStates();
  672. let dataList = []
  673. dataList = states
  674. if (dataList.length > 0){
  675. this.projectTile = dataList[0].data.stagename
  676. this.sa_projstagemagid = dataList[0].data.sa_projstagemagid
  677. this.projectParam.content.pageNumber = 1
  678. this.projectParam.content.pageSize = 20
  679. this.getProjectList()
  680. }
  681. })
  682. this.getProportionOfFileModel()
  683. } else {
  684. /*const colorArray = ['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#6FD26C','#DFC064'];*/
  685. const colorArray = ['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#6FD26C','#DFC064'];
  686. this.chartPie = new Funnel('containerFunnel', {
  687. data: this.tableData,
  688. maxSize:0.6,
  689. xField: 'stagename',
  690. yField: 'wide',
  691. shape: 'funnel',
  692. dynamicHeight: false,
  693. legend: false,
  694. interactions: [{ type: 'element-active'}],
  695. color:['#6395fa','#63daab','#657798','#f7c122','#7666fa','#75cbed','#6FD26C','#DFC064','#fff'],
  696. label: {
  697. layout:"fixedOverlap",
  698. position:'right',
  699. offsetX:40,
  700. content:(datum)=>{
  701. const group = new G.Group({});
  702. const content = ()=>{
  703. if (this.tableData[0]) {
  704. const text = `${datum.stagename} 项目总数: ${datum.sequence1} 当前项目数: ${datum.projectqty} 转化率: ${datum.zhl?Math.round((datum.zhl* 100)*100)/100 + '%':'--'} 预计签约金额: ${datum.signamount_due}万元 项目成交金额: ${datum.dealamount}万元`
  705. const lines = text.split(' ');
  706. return lines.join('\n');
  707. }
  708. };
  709. const color = ()=>{
  710. let clr = ''
  711. this.tableData.some((e,index) =>{
  712. if (e.stagename == datum.stagename) {
  713. clr = colorArray[index]
  714. }
  715. })
  716. if (clr == '' || clr == undefined) {
  717. clr= '#ffffff'
  718. }
  719. return clr
  720. };
  721. group.addShape({
  722. type: 'text',
  723. attrs: {
  724. x: 40,
  725. y: 0,
  726. text: content(),
  727. textAlign: 'left',
  728. fontSize: 14,
  729. textBaseline: 'top',
  730. fill: color()
  731. },
  732. });
  733. return group;
  734. },
  735. },
  736. tooltip:{
  737. customContent: (title, items) => {
  738. // 构建自定义内容
  739. const content = `<div>
  740. <ul style="padding:10px;">
  741. ${items.map((item) => `
  742. <li>
  743. <p style="margin-bottom:10px">${title}</p>
  744. <p>项目总数:${item.data.projectqty}</p>
  745. </li>`).join('')}
  746. </ul>
  747. </div>`;
  748. return content;
  749. },
  750. },
  751. conversionTag: false,
  752. funnelStyle: {
  753. stroke: '#fff',
  754. lineWidth: 3,
  755. },
  756. });
  757. this.chartPie.render();
  758. document.addEventListener('click',(evt) => {
  759. const states = this.chartPie.getStates();
  760. let dataList = []
  761. dataList = states
  762. if (dataList.length > 0 && dataList[0].data.stagename !== ''){
  763. this.projectTile = dataList[0].data.stagename
  764. this.sa_projstagemagid = dataList[0].data.sa_projstagemagid
  765. this.projectParam.content.pageNumber = 1
  766. this.projectParam.content.pageSize = 20
  767. this.getProjectList()
  768. }
  769. })
  770. this.getProportionOfFileModel()
  771. }
  772. },
  773. async getProportionOfFileModel () {
  774. const res = await this.$api.requested(this.dataParam)
  775. this.tableData = res.data
  776. let dataList = []
  777. dataList = res.data
  778. this.sa_projstagemagid = dataList[0].sa_projstagemagid
  779. this.projectTile = dataList[0].stagename
  780. this.projectParam.content.pageNumber = 1
  781. this.projectParam.content.pageSize = 20
  782. this.getProjectList()
  783. sessionStorage.setItem('flagIndex',dataList.length)
  784. this.flagIndex = dataList.length
  785. this.chartPie.changeData(res.data)
  786. dataList.splice(dataList.length-1)
  787. if (this.siteid == 'HY' || this.siteid == 'YOSTEST1'){
  788. this.expectedTransaction(false)
  789. }
  790. },
  791. async getProjectList(){
  792. this.projectParam.content.type = this.dataParam.content.type
  793. this.projectParam.content.dataid = this.dataParam.content.dataid
  794. /*this.projectParam.content.dateType = this.dataParam.content.dateType*/
  795. this.projectParam.content.sa_projstagemagid = this.sa_projstagemagid
  796. const res = await this.$api.requested(this.projectParam)
  797. this.projectList = res.data
  798. this.total = res.total
  799. },
  800. handleSizeChange(val) {
  801. // console.log(`每页 ${val} 条`);
  802. this.projectParam.content.pageSize = val
  803. this.getProjectList()
  804. },
  805. handleCurrentChange(val) {
  806. // console.log(`当前页: ${val}`);
  807. this.projectParam.content.pageNumber = val
  808. this.getProjectList()
  809. },
  810. /*获取领域*/
  811. async queryTradeField(){
  812. const res = await this.$store.dispatch('optiontypeselect','tradefield')
  813. this.tradefields = res.data
  814. },
  815. /*项目成交数据*/
  816. async expectedTransaction(init){
  817. let param = {
  818. "id": 20241028162104,
  819. "content": {
  820. "type": "0",
  821. "dataid": "0",
  822. "where": {
  823. "tradefield": "",
  824. "isleave":""
  825. }
  826. }
  827. }
  828. param.content.type = this.dataParam.content.type
  829. param.content.dataid = this.dataParam.content.dataid
  830. param.content.where.tradefield = this.dataParam.content.where.tradefield
  831. param.content.where.isleave = this.dataParam.content.where.isleave
  832. const res = await this.$api.requested(param)
  833. console.log(res.data,'数据项目成交')
  834. this.previousData = [
  835. {
  836. title:'成交项目数',
  837. value:res.data.dealTotalCount,
  838. unit:'个',
  839. description:'当前状态为已成交,并且项目成交时间在前12个月(不含当前月)的项目数量',
  840. color:'#3874F6'
  841. },
  842. {
  843. title:'预计成交正偏差',
  844. title1:'项目',
  845. value1:res.data.positiveCount,
  846. unit1:'个',
  847. title2:'金额',
  848. value2:this.tool.formatAmount(this.tool.unitConversion(res.data.positiveOffsetAmount,10000),2),
  849. unit2:'万元',
  850. description:'依据:每个项目的偏差金额 = 项目成交金额 - 预计签约金额' + '\n' + ' ①项目:合计偏差金额为正数的项目数量' + '\n' + ' ②金额:合计每个项目的正数偏差金额',
  851. color:'#E6A23C'
  852. },
  853. {
  854. title:'项目成交金额合计',
  855. value:this.tool.formatAmount(this.tool.unitConversion(res.data.dealAmount,10000),2),
  856. unit:'万元',
  857. description:'合计当前状态为已成交,并且项目成交时间在前12个月(不含当前月)的项目订单金额',
  858. color: '#009966'
  859. },
  860. {
  861. title:'失败项目数',
  862. value:res.data.failTotalCount,
  863. unit:'个',
  864. description:'当前状态为已失败,并且失败操作时间在前12个月(不含当前月)的项目数量',
  865. color:'#3874F6'
  866. },
  867. {
  868. title:'预计成交负偏差',
  869. title1:'项目',
  870. value1:res.data.negativeCount,
  871. unit1:'个',
  872. title2:'金额',
  873. value2:this.tool.formatAmount(this.tool.unitConversion(res.data.negativeOffsetAmount,10000),2),
  874. unit2:'万元',
  875. description:'依据:每个项目的偏差金额 = 项目成交金额 - 预计签约金额' + '\n' + ' ①项目:合计偏差金额为负数的项目数量' + '\n' + ' ②金额:合计每个项目的负数偏差金额',
  876. color:'#E6A23C'
  877. },
  878. {
  879. title:'预计签约金额合计',
  880. value:this.tool.formatAmount(this.tool.unitConversion(res.data.signAmount,10000),2),
  881. unit:'万元',
  882. description:'合计当前状态为已成交,并且项目成交时间在前12个月(不含当前月)的项目预计签约金额',
  883. color: '#009966'
  884. },
  885. {
  886. title:'项目成交率',
  887. value:Math.round((res.data.dealRate * 100)*100)/100,
  888. unit:'%',
  889. description:'项目成交率 = 成交项目数 ÷ (成交项目数 + 失败项目数)×100%',
  890. color:'#3874F6'
  891. },
  892. {
  893. title:'预计成交准确率',
  894. value:Math.round((res.data.rightRate * 100)*100)/100,
  895. unit:'%',
  896. description:'依据:偏差率 = |(项目成交金额 - 预计签约金额)| ÷ 预计签约金额 × 100%' + '\n' + ' 预计成交准确率 = 偏差率≤15%的项目数 ÷ 成交项目数 × 100%',
  897. color:'#E6A23C'
  898. },
  899. ]
  900. this.futreData = res.data.array
  901. this.$refs.futureTwelveMonthsRef.chartData(init,this.futreData)
  902. },
  903. },
  904. mounted () {
  905. /* this.renderPie()*/
  906. this.departmentrtment()
  907. this.dateSet(99)
  908. this.queryTradeField()
  909. },
  910. created() {
  911. this.tablecols = this.tool.tabelCol(this.$route.name).projectTable.tablecols
  912. this.tablecolsData = this.tool.tabelCol(this.$route.name).tableDatas.tablecols
  913. }
  914. }
  915. </script>
  916. <style>
  917. </style>
  918. <style scoped>
  919. .title{
  920. height: 20px;
  921. line-height: 20px;
  922. font-size: 14px;
  923. text-indent: 7px;
  924. font-weight: bold;
  925. color: #333333;
  926. margin-bottom: 20px;
  927. border-left: .3rem solid #3874F6;
  928. }
  929. .container{
  930. /* height:calc(100vh)*/
  931. }
  932. .statistics-box{
  933. width: 49%;
  934. background: #FFFFFF;
  935. box-shadow: 0px 1px 6px 1px rgba(0,0,0,0.16);
  936. border-radius: 10px 10px 10px 10px;
  937. box-sizing: border-box;
  938. }
  939. .statistics-box .statistics-box-title{
  940. font-family: Microsoft YaHei, Microsoft YaHei;
  941. font-weight: bold;
  942. font-size: 16px;
  943. color: #333333;
  944. line-height: 22px;
  945. text-align: left;
  946. font-style: normal;
  947. text-transform: none;
  948. }
  949. .screen-box{
  950. width: 100%;
  951. height: 302px;
  952. background: #FFFFFF;
  953. box-shadow: 0px 1px 6px 1px rgba(0,0,0,0.16);
  954. border-radius: 5px 5px 5px 5px;
  955. box-sizing: border-box;
  956. }
  957. .title-margin-15{
  958. margin-top: 15px;
  959. }
  960. .title-margin-5{
  961. margin-top: 5px;
  962. }
  963. .title-font-style1{
  964. font-family: Microsoft YaHei, Microsoft YaHei;
  965. font-weight: 400;
  966. font-size: 14px;
  967. color: #888888;
  968. text-align: left;
  969. font-style: normal;
  970. text-transform: none;
  971. }
  972. .title-font-style2{
  973. font-family: Microsoft YaHei, Microsoft YaHei;
  974. font-weight: bold;
  975. font-size: 18px;
  976. color: #009966;
  977. text-align: left;
  978. font-style: normal;
  979. text-transform: none;
  980. }
  981. .title-font-style3{
  982. font-family: Microsoft YaHei, Microsoft YaHei;
  983. font-weight: 400;
  984. font-size: 14px;
  985. color: #003399;
  986. text-align: left;
  987. font-style: normal;
  988. text-transform: none;
  989. }
  990. .title-font-style4{
  991. font-family: Microsoft YaHei, Microsoft YaHei;
  992. font-weight: bold;
  993. font-size: 12px;
  994. color: #009966;
  995. text-align: left;
  996. font-style: normal;
  997. text-transform: none;
  998. }
  999. .title-font-style5{
  1000. font-family: Microsoft YaHei, Microsoft YaHei;
  1001. font-weight: 400;
  1002. font-size: 16px;
  1003. color: #333333;
  1004. text-align: left;
  1005. font-style: normal;
  1006. text-transform: none;
  1007. padding-top: 20px;
  1008. }
  1009. </style>