index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <div class="course-red-packet-statistics">
  3. <!-- 查询条件 -->
  4. <el-card class="search-card">
  5. <el-form :model="queryParams" ref="queryParams" label-width="80px" inline>
  6. <el-form-item label="筛选类型" prop="status">
  7. <el-select v-model="queryParams.changeType" placeholder="请选择筛选类型" clearable style="width: 100%" @change="changeType">
  8. <el-option label="按销售公司" :value="1"></el-option>
  9. <el-option label="按员工" :value="2"></el-option>
  10. </el-select>
  11. </el-form-item>
  12. <el-form-item label="公司名" prop="companyId" v-if="queryParams.changeType ==1 ">
  13. <el-select filterable style="width: 220px" v-model="queryParams.companyIds"
  14. @change="handleSeller" placeholder="请选择公司名"
  15. clearable size="small"
  16. multiple >
  17. <el-option
  18. v-for="item in companys"
  19. :key="item.companyId"
  20. :label="item.companyName"
  21. :value="item.companyId"
  22. />
  23. </el-select>
  24. </el-form-item>
  25. <el-form-item label="选择员工" prop="userIds" v-if="queryParams.changeType ==2 ">
  26. <select-tree
  27. v-model="selectedCompanyList"
  28. :raw-data="deptList"
  29. :parentSelectable="true"
  30. placeholder="请选择销售"
  31. :multiple="true"
  32. component-width="300px"
  33. :max-display-tags="3"
  34. :check-strictly="false"
  35. :return-leaf-only="false"
  36. ></select-tree>
  37. </el-form-item>
  38. <el-form-item label="状态" prop="status">
  39. <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
  40. <el-option label="全部" value=""></el-option>
  41. <el-option label="发送中" value="0"></el-option>
  42. <el-option label="已发送" value="1"></el-option>
  43. </el-select>
  44. </el-form-item>
  45. <el-form-item label="创建时间" prop="createTime">
  46. <el-date-picker
  47. v-model="createTimeText"
  48. type="datetimerange"
  49. range-separator="至"
  50. start-placeholder="开始日期"
  51. end-placeholder="结束日期"
  52. value-format="yyyy-MM-dd HH:mm:ss"
  53. @change="createChange"
  54. :default-time="['00:00:00', '23:59:59']"
  55. />
  56. </el-form-item>
  57. <el-row :gutter="10" class="mb8">
  58. <el-col :span="1.5">
  59. <el-button
  60. type="warning"
  61. plain
  62. icon="el-icon-download"
  63. size="mini"
  64. :loading="exportLoading"
  65. @click="handleExport"
  66. v-hasPermi="['course:courseRedPacketLog:countExport']"
  67. >导出
  68. </el-button>
  69. </el-col>
  70. <el-col :span="24" class="text-right">
  71. <el-button type="primary" @click="handleSearch">查询</el-button>
  72. <el-button @click="resetQuery">重置</el-button>
  73. </el-col>
  74. </el-row>
  75. </el-form>
  76. </el-card>
  77. <!-- 统计结果 -->
  78. <el-card class="result-card">
  79. <div slot="header">
  80. <span>红包发送统计</span>
  81. </div>
  82. <el-table :data="statisticsData" border style="width: 100%" v-loading="loading"
  83. show-summary :summary-method="getSummaries"> >
  84. <el-table-column prop="companyName" label="公司名称" align="center"></el-table-column>
  85. <div v-if="queryParams.changeType==2">
  86. <el-table-column prop="nickName" label="员工姓名" align="center"></el-table-column>
  87. <el-table-column prop="companyUserId" label="员工编号" align="center"></el-table-column>
  88. <el-table-column prop="deptId" label="部门编号" align="center"></el-table-column>
  89. <el-table-column prop="deptName" label="部门昵称" align="center"></el-table-column>
  90. </div>
  91. <el-table-column prop="amount" label="已发红包金额" align="center"></el-table-column>
  92. </el-table>
  93. <pagination
  94. v-show="total>0"
  95. :total="total"
  96. :page.sync="queryParams.pageNum"
  97. :limit.sync="queryParams.pageSize"
  98. @pagination="getList"
  99. />
  100. </el-card>
  101. </div>
  102. </template>
  103. <script>
  104. import {getCompanyList} from "@/api/company/company";
  105. import {getUserList} from "@/api/company/companyUser";
  106. import SelectTree from '@/components/TreeSelect/index.vue'
  107. import { getDeptData } from '@/api/system/employeeStats'
  108. import {
  109. exportCourseRedPacketLogCountExport,
  110. getRedPacketLogCount
  111. } from '@/api/course/courseRedPacketLog'
  112. export default {
  113. name: 'courseRedPacketLogCount',
  114. components: { SelectTree },
  115. data() {
  116. return {
  117. // 总条数
  118. total: 0,
  119. companys:[],
  120. companyUserList:[],
  121. selectedCompanyList: [],
  122. deptList: [],
  123. createTimeText: [],
  124. queryParams: {
  125. pageNum: 1,
  126. pageSize: 10,
  127. changeType: 1,
  128. companyId: null,
  129. companyIds: null,
  130. companyUserId: null,
  131. startTime:null,
  132. endTime:null,
  133. status: null,
  134. sTime:null,
  135. eTime:null,
  136. userIds: null
  137. },
  138. exportLoading: false,
  139. statisticsData: [],
  140. loading: false,
  141. pageInfo: {
  142. currentPage: 1,
  143. pageSize: 10,
  144. total: 0
  145. }
  146. }
  147. },
  148. mounted() {
  149. },
  150. created() {
  151. getCompanyList().then(response => {
  152. this.companys = response.data;
  153. if(this.companys!=null&&this.companys.length>0){
  154. this.companyId=this.companys[0].companyId;
  155. }
  156. });
  157. getDeptData().then(response => {
  158. this.deptList = response.data;
  159. })
  160. },
  161. methods: {
  162. changeType(){
  163. this.statisticsData=[];
  164. if (this.queryParams.changeType == 1){
  165. this.selectedCompanyList=[];
  166. }
  167. if (this.queryParams.changeType == 2){
  168. this.queryParams.companyIds=null;
  169. }
  170. },
  171. getSummaries(param) {
  172. const { columns, data } = param; // data 就是当前表格的 statisticsData
  173. const sums = [];
  174. columns.forEach((column, index) => {
  175. if (index === 0) {
  176. sums[index] = '总计';
  177. return;
  178. }
  179. if (column.property === 'amount') {
  180. // 直接从当前表格数据计算
  181. const total = data.reduce((sum, item) => {
  182. return sum + (parseFloat(item.amount) || 0);
  183. }, 0);
  184. sums[index] = '¥' + this.formatAmount(total);
  185. } else {
  186. sums[index] = '';
  187. }
  188. });
  189. return sums;
  190. },
  191. // 格式化金额(不四舍五入,保留两位小数)
  192. formatAmount(value) {
  193. if (isNaN(value)) return '0.00';
  194. // 方法1:使用 Math.floor 截断
  195. const truncated = Math.floor(value * 100) / 100;
  196. // 方法2:更精确的截断方法
  197. // const truncated = Number(value.toString().match(/^\d+(?:\.\d{0,2})?/));
  198. return truncated.toFixed(2);
  199. },
  200. handleSearch() {
  201. this.pageInfo.currentPage = 1
  202. this.getList();
  203. },
  204. createChange(createTime) {
  205. if (createTime && createTime.length >= 2) {
  206. if(!this.checkDateRangeLimit(createTime)){
  207. this.createTimeText = null;
  208. this.queryParams.sTime=null;
  209. this.queryParams.eTime=null;
  210. return;
  211. }
  212. this.queryParams.sTime = this.formatDate(createTime[0]) || null;
  213. this.queryParams.eTime = this.formatDate(createTime[1]) || null;
  214. } else {
  215. this.createTimeText = [];
  216. this.queryParams.sTime = null;
  217. this.queryParams.eTime = null;
  218. }
  219. },
  220. formatDate(date) {
  221. if (!date) return ''
  222. // 确保 date 是 Date 对象
  223. let dateObj = date
  224. if (typeof date === 'string') {
  225. dateObj = new Date(date)
  226. }
  227. // 如果转换失败,返回空字符串
  228. if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
  229. return ''
  230. }
  231. // 使用更安全的格式化方法
  232. const year = dateObj.getFullYear()
  233. const month = String(dateObj.getMonth() + 1).padStart(2, '0')
  234. const day = String(dateObj.getDate()).padStart(2, '0')
  235. const hours = String(dateObj.getHours()).padStart(2, '0')
  236. const minutes = String(dateObj.getMinutes()).padStart(2, '0')
  237. const seconds = String(dateObj.getSeconds()).padStart(2, '0')
  238. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  239. },
  240. checkDateRangeLimit(dateRange) {
  241. if (dateRange && dateRange.length >= 2) {
  242. const startDate = new Date(dateRange[0]);
  243. const endDate = new Date(dateRange[1]);
  244. // 设置时间为当天开始,避免时间部分影响计算
  245. startDate.setHours(0, 0, 0, 0);
  246. endDate.setHours(0, 0, 0, 0);
  247. const timeDiff = Math.abs(endDate - startDate);
  248. const diffDays = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  249. if (diffDays > 31) { // maxDays-1 因为包含起始日
  250. this.$message.warning('时间区间不能超过一个月');
  251. return false;
  252. }
  253. }
  254. return true;
  255. },
  256. handleSeller(){
  257. if(this.queryParams.companyId != null) {
  258. getUserList(this.queryParams.companyId).then(res=>{
  259. if(res.code === 200) {
  260. this.companyUserList = res.data
  261. }
  262. })
  263. }
  264. },
  265. resetQuery() {
  266. this.$refs.queryParams.resetFields();
  267. this.createTimeText = [];
  268. this.queryParams.dateRange = [];
  269. this.selectedCompanyList = [];
  270. this.statisticsData=[];
  271. this.handleSearch()
  272. },
  273. getList() {
  274. if (this.queryParams.changeType == 1 && this.queryParams.companyIds.length == 0) {
  275. return this.$message.warning('请选择公司');
  276. }
  277. if (this.isEmptyArray(this.createTimeText)) {
  278. this.$message.warning('请选择创建时间');
  279. return;
  280. }
  281. console.log(this.queryParams.changeType)
  282. console.log(this.selectedCompanyList)
  283. if (this.queryParams.changeType == 2){
  284. if( this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
  285. this.queryParams.userIds = this.selectedCompanyList;
  286. }else {
  287. return this.$message.warning('请选择员工');
  288. }
  289. }
  290. this.loading = true
  291. // 查询统计
  292. getRedPacketLogCount(this.queryParams).then(response => {
  293. this.statisticsData = response.data.list;
  294. this.total = response.data.total;
  295. this.loading = false;
  296. });
  297. },
  298. // 添加辅助方法
  299. isEmptyArray(arr) {
  300. return !arr || arr.length === 0;
  301. },
  302. handleSizeChange(val) {
  303. this.pageInfo.pageSize = val
  304. this.pageInfo.currentPage = 1
  305. this.getList()
  306. },
  307. handleCurrentChange(val) {
  308. this.pageInfo.currentPage = val
  309. this.getList()
  310. },
  311. /** 导出按钮操作 */
  312. handleExport() {
  313. if(this.selectedCompanyList != null && this.selectedCompanyList.length > 0) {
  314. this.queryParams.userIds = this.selectedCompanyList;
  315. }else {
  316. this.queryParams.userIds = [];
  317. }
  318. const queryParams = this.queryParams;
  319. this.$confirm('是否确认导出所有红包记录数据项?', "警告", {
  320. confirmButtonText: "确定",
  321. cancelButtonText: "取消",
  322. type: "warning"
  323. }).then(() => {
  324. const loadingInstance = this.$loading({
  325. lock: true,
  326. text: '正在导出数据,请稍候...',
  327. background: 'rgba(0, 0, 0, 0.7)'
  328. });
  329. this.exportLoading = true;
  330. return exportCourseRedPacketLogCountExport(queryParams).finally(res=>{
  331. loadingInstance.close();
  332. })
  333. }).then(response => {
  334. this.download(response.msg);
  335. this.exportLoading = false;
  336. }).catch(() => {}).finally(res=>{
  337. });
  338. }
  339. }
  340. }
  341. </script>
  342. <style lang="scss" scoped>
  343. .course-red-packet-statistics {
  344. padding: 20px;
  345. .search-card {
  346. margin-bottom: 20px;
  347. }
  348. .result-card {
  349. .pagination-container {
  350. margin-top: 20px;
  351. text-align: right;
  352. }
  353. }
  354. .text-right {
  355. text-align: right;
  356. }
  357. }
  358. </style>