equityDataSummary.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <template>
  2. <div class="data-summary-container">
  3. <el-card>
  4. <div style="display: flex">
  5. <div class="data-summary-top">
  6. <span :class="['button-sty', { act: monthType === item.label }]" v-for="item in monthLabel" @click="toggleMonth(item.label)" :key="item.label">
  7. {{ item.label }}
  8. </span>
  9. </div>
  10. <date-picker v-model="timeRange" type="year" range value-type="format" placeholder="开始至结束" :editable="false" @change="dateChange" :disabled-date="disabledDate" @pick="onPick" clearable />
  11. </div>
  12. </el-card>
  13. <div class="dataReport-main">
  14. <!-- 合同类型帅选项 -->
  15. <div style="display: flex; flex-wrap: wrap">
  16. <div class="data-summary-top">
  17. <span :class="['button-sty btn-filter', { act: selectedFilters.includes(item) }]" v-for="(fields, item) in filterOptions" @click="toggleFilter(item)" :key="item">
  18. {{ item }}
  19. </span>
  20. </div>
  21. <el-cascader
  22. v-model="pathfinderSales"
  23. :options="salesArrRai"
  24. :props="salesArrProps"
  25. clearable
  26. @change="salesChangeHandedl"
  27. placeholder="选择开拓组销售"
  28. :disabled="serviceGroupSalesAct.length"
  29. style="margin-right: 20px"
  30. ></el-cascader>
  31. <el-cascader
  32. v-model="serviceGroupSalesAct"
  33. :options="serviceGroupSales"
  34. :props="salesArrProps"
  35. clearable
  36. @change="salesChangeHandedl"
  37. placeholder="选择服务组销售"
  38. :disabled="pathfinderSales.length"
  39. ></el-cascader>
  40. <div class="switch-box">
  41. <span>开拓组</span>
  42. <el-switch v-model="pathfinderSwitch" :disabled="isSwitchDisabled" @change="salesChangeHandedl"></el-switch>
  43. <span>服务组</span>
  44. <el-switch v-model="serviceGroupSwitch" :disabled="isSwitchDisabled" @change="salesChangeHandedl"></el-switch>
  45. </div>
  46. </div>
  47. <!-- 数据表格 -->
  48. <el-table :data="flattenedData" border style="width: 100%; margin-top: 20px" :span-method="mergeRows">
  49. <!-- DataType 作为合并行 -->
  50. <el-table-column prop="DataType" label="" align="center" width="120px" />
  51. <!-- 销售姓名 -->
  52. <el-table-column prop="SellerName" label="销售姓名" align="center" width="150px" />
  53. <el-table-column v-for="col in dynamicColumns" :key="col.key" :prop="col.key" align="center">
  54. <template slot="header" slot-scope="{ row }">
  55. <el-tooltip :content="col.tooltip" placement="top">
  56. <div style="display: inline-flex; align-items: center">
  57. <span style="margin-right: 4px">{{ col.label }}</span>
  58. <i class="el-icon-info" style="color: #333333" />
  59. </div>
  60. </el-tooltip>
  61. </template>
  62. <template slot-scope="{ row }">
  63. <span class="editsty" @click="handlerRowClick(row, col.key)">{{ row[col.key] }}</span>
  64. </template>
  65. </el-table-column>
  66. </el-table>
  67. </div>
  68. <DataSummary :visible.sync="showDlg" :title="titleDlg" :columns="columnsDlg" :dataItem="dataItem" />
  69. </div>
  70. </template>
  71. <script>
  72. import { dataMainInterface } from "@/api/api.js";
  73. import DataSummary from "./components/DataSummary.vue";
  74. import { tableConfigs, tableDlgTitle } from "./configdata.js";
  75. export default {
  76. components: { DataSummary },
  77. data() {
  78. const currentYear = new Date().getFullYear() + "";
  79. return {
  80. timeRange: [currentYear, currentYear],
  81. timePick: "",
  82. monthType: "季度",
  83. monthLabel: [
  84. {
  85. label: "季度",
  86. },
  87. {
  88. label: "半年度",
  89. },
  90. {
  91. label: "年度",
  92. },
  93. ],
  94. // 筛选项映射的表头字段(新增后端 key)
  95. filterOptions: {
  96. 新签: [
  97. { label: "新增试用", key: "AddTrialCount", tooltip: "新增试用客户的时间,包含在所选时间段内的客户数(包括新建和领取流失)" },
  98. { label: "新签合同(金额/数量)", key: "NewContractData", tooltip: "起始时间在所选时间段内的新签合同(第一份合同起始时间一年内的再次签约仍属于新签合同)" },
  99. ],
  100. 续约: [
  101. { label: "到期合同(金额/数量)", key: "ExpiredContractData", tooltip: "结束时间在所选时间段内的合用" },
  102. { label: "续约合同(金额/数量)", key: "RenewedContractData", tooltip: "起始时间在所选时间段内的续约合同(第一份合同起始时间一年以后的再次签约均属于续约合同)" },
  103. { label: "续约率(金额/数量)", key: "RenewalRateData", tooltip: "续约合同/到期合同" },
  104. { label: "确认不续约合同(金额/数量)", key: "ConfirmedNoRenewalContractData", tooltip: "已确认不续约的到期合同" },
  105. { label: "确认不续约率(金额/数量)", key: "ConfirmNonRenewalRateData", tooltip: "确认不续约合同(剔除非业务不续约)/到期合同" },
  106. { label: "签约客户数量", key: "SignedClientCount", tooltip: "新签和续约的客户数" },
  107. { label: "客单价", key: "AverageRevenueCount", tooltip: "新签合同和续约合同的总金额/签约客户数量" },
  108. ],
  109. 收入: [
  110. { label: "开票金额", key: "InvoiceAmountCount", tooltip: "财务系统中已经登记开票的金额" },
  111. { label: "到款金额", key: "PaymentReceivedCount", tooltip: "财务系统中已经登记到账的金额" },
  112. { label: "未到款比例", key: "UnpaidRatioCount", tooltip: "开票金额-到款金额 / 开票金额" },
  113. { label: "新客开票", key: "NewCustomerInvoicingCount", tooltip: "财务系统中已经登记开票的新签合同金额" },
  114. { label: "新客到款", key: "NewCustomerPaymentsReceivedCount", tooltip: "财务系统中已经登记开票的新签合同金额" },
  115. ],
  116. },
  117. selectedFilters: ["新签", "续约", "收入"], // 用户选择的筛选项
  118. // 示例数据(与后端返回的 key 对应)
  119. tableData: [],
  120. // 扁平化后的数据
  121. flattenedData: [],
  122. showDlg: false,
  123. titleDlg: "",
  124. columnsDlg: [],
  125. salesArrRai: [], //开拓者销售
  126. pathfinderSales: [], //选中的开拓者销售
  127. salesArrProps: {
  128. multiple: true,
  129. value: "AdminId",
  130. label: "RealName",
  131. children: "ChildrenList",
  132. },
  133. serviceGroupSales: [], //服务组销售
  134. serviceGroupSalesAct: [], //选中的服务组销售
  135. pathfinderSwitch: false, //开拓者开关
  136. serviceGroupSwitch: false, //服务组开关
  137. dataItem: {}, //数据项
  138. };
  139. },
  140. methods: {
  141. /* 选择时间后的处理 */
  142. dateChange() {
  143. this.$nextTick(() => {
  144. if (this.timeRange.length) this.timePick = this.timeRange[0];
  145. this.getDataList();
  146. });
  147. },
  148. // 处理结束年份变化的逻辑
  149. disabledDate(time) {
  150. if (this.timePick) {
  151. const minTime = +this.timePick;
  152. const maxTime = +this.timePick + 4; // 结束年份最大为开始年份限制5年 +4
  153. return this.$moment(time).format("YYYY") < minTime || this.$moment(time).format("YYYY") > maxTime;
  154. }
  155. },
  156. // 选了时间的事件
  157. onPick(date) {
  158. this.timePick = this.$moment(date).format("YYYY");
  159. },
  160. // 获取表格数据
  161. async getDataList() {
  162. let AdminId = this.pathfinderSales.map((item) => item[item.length - 1]).join(",");
  163. let ServiceAdminId = this.serviceGroupSalesAct.map((item) => item[item.length - 1]).join(",");
  164. let params = {
  165. StartYear: this.timeRange[0],
  166. EndYear: this.timeRange[1],
  167. DataType: this.monthType,
  168. AdminId,
  169. ServiceAdminId,
  170. DevelopButton: this.pathfinderSwitch,
  171. ServerButton: this.serviceGroupSwitch,
  172. };
  173. const res = await dataMainInterface.getRaiDataSummary(params);
  174. this.tableData = res.Data.List;
  175. this.flattenedData = this.tableData.flatMap((item) => item.DataList.map((row) => ({ ...row, DataType: item.DataType })));
  176. },
  177. // 处理合并行
  178. mergeRows({ row, column, rowIndex, columnIndex }) {
  179. if (columnIndex === 0) {
  180. // 只合并 DataType (第1列)
  181. const currentType = row.DataType;
  182. const prevType = rowIndex > 0 ? this.flattenedData[rowIndex - 1].DataType : null;
  183. if (rowIndex === 0 || currentType !== prevType) {
  184. // 计算 rowspan(找到相同 DataType 的总行数)
  185. const rowspan = this.flattenedData.filter((item) => item.DataType === currentType).length;
  186. return { rowspan, colspan: 1 };
  187. } else {
  188. return { rowspan: 0, colspan: 0 };
  189. }
  190. }
  191. },
  192. // 点击表格
  193. handlerRowClick(row, key) {
  194. console.log(row);
  195. this.showDlg = true;
  196. this.dataItem = row;
  197. this.titleDlg = tableDlgTitle[key];
  198. this.columnsDlg = tableConfigs[key];
  199. },
  200. // 点击头部筛选项
  201. toggleMonth(key) {
  202. if (this.monthType !== key) {
  203. this.monthType = key;
  204. this.getDataList();
  205. }
  206. },
  207. // 点击了标签
  208. toggleFilter(item) {
  209. if (this.selectedFilters.includes(item)) {
  210. let index = this.selectedFilters.findIndex((key) => key === item);
  211. this.selectedFilters.splice(index, 1);
  212. } else {
  213. this.selectedFilters.push(item);
  214. }
  215. this.getDataList();
  216. },
  217. /* 获取权益销售 */
  218. getSaleRai() {
  219. dataMainInterface.getListRaiSellerServer().then((res) => {
  220. if (res.Ret === 200) {
  221. this.salesArrRai = res.Data.List;
  222. this.serviceGroupSales = res.Data.ListServer;
  223. }
  224. });
  225. },
  226. salesChangeHandedl() {
  227. this.getDataList();
  228. },
  229. },
  230. computed: {
  231. // 计算动态表头
  232. dynamicColumns() {
  233. let columns = [];
  234. this.selectedFilters.forEach((key) => {
  235. if (this.filterOptions[key]) {
  236. this.filterOptions[key].forEach((field) => {
  237. columns.push({ label: field.label, key: field.key, tooltip: field.tooltip });
  238. });
  239. }
  240. });
  241. return columns;
  242. },
  243. // 开关选择是否禁用
  244. isSwitchDisabled() {
  245. return this.pathfinderSales.length || this.serviceGroupSalesAct.length;
  246. },
  247. },
  248. mounted() {
  249. this.getDataList();
  250. this.getSaleRai();
  251. },
  252. };
  253. </script>
  254. <style scoped lang="scss">
  255. .data-summary-container {
  256. .data-summary-top {
  257. box-sizing: border-box;
  258. height: 40px;
  259. border: #dcdfe6 1px solid;
  260. border-radius: 4px;
  261. margin-right: 20px;
  262. overflow: hidden;
  263. }
  264. .button-sty {
  265. display: inline-block;
  266. text-align: center;
  267. line-height: 40px;
  268. width: 120px;
  269. height: 40px;
  270. cursor: pointer;
  271. &:nth-child(2) {
  272. border-left: 1px solid #dcdfe6;
  273. border-right: 1px solid #dcdfe6;
  274. }
  275. }
  276. .act {
  277. background-color: #409eff !important;
  278. color: #fff !important;
  279. }
  280. .dataReport-main {
  281. padding: 20px 20px 80px;
  282. background: #fff;
  283. margin-top: 20px;
  284. border: 1px solid #ececec;
  285. border-radius: 4px;
  286. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.05);
  287. .btn-filter {
  288. background-color: #eaf3fe;
  289. color: #333;
  290. }
  291. .switch-box {
  292. display: flex;
  293. align-items: center;
  294. span {
  295. flex-shrink: 0;
  296. display: inline-block;
  297. margin: 0 10px 0 20px;
  298. }
  299. }
  300. }
  301. }
  302. .dete-partition {
  303. display: inline-block;
  304. margin: 0 10px;
  305. }
  306. </style>