Browse Source

feat: 群发送达数据

zhengxy 1 year ago
parent
commit
eb78c8d5b3

+ 4 - 0
project/src/assets/config/interface_api.js

@@ -494,6 +494,10 @@ var api = {
494 494
   groupTransfer_configDetail: '/api/chatGroupTransfer/configDetail', // 客户群分配 - 配置详情
495 495
   groupTransfer_record: '/api/chatGroupTransfer/record', // 客户群分配 - 分配结果
496 496
 
497
+  sendData_massMsg: '/api/service/massMsg', // 群发送达数据 - 客户群发
498
+  sendData_chatGroupMassMsg: '/api/service/chatGroupMassMsg', // 群发送达数据 - 客户群群发
499
+  sendData_PeriodMassMsg: '/api/service/periodMassMsg', // 群发送达数据 - 智能群发
500
+
497 501
 };
498 502
 
499 503
 export { api };

+ 47 - 0
project/src/components/dataBoard/sendData/index.vue

@@ -0,0 +1,47 @@
1
+<template>
2
+  <div>
3
+    <div class="topTagBox flex">
4
+      <div class="left flex-align-center">
5
+        <div
6
+          :class="['tagItem', type == 'khqf' ? 'tagItem_active' : '']"
7
+          @click="changeType('khqf')"
8
+        >
9
+          客户群发
10
+        </div>
11
+        <div
12
+          :class="['tagItem', type == 'khqqf' ? 'tagItem_active' : '']"
13
+          @click="changeType('khqqf')"
14
+        >
15
+          客户群群发
16
+        </div>
17
+        <div
18
+          :class="['tagItem', type == 'znqf' ? 'tagItem_active' : '']"
19
+          @click="changeType('znqf')"
20
+        >
21
+          智能群发
22
+        </div>
23
+      </div>
24
+    </div>
25
+    <keep-alive>
26
+      <component v-bind:is="type"></component>
27
+    </keep-alive>
28
+  </div>
29
+</template>
30
+<script>
31
+import khqf from "./khqf.vue";
32
+import khqqf from "./khqqf.vue";
33
+import znqf from "./znqf.vue";
34
+export default {
35
+  components: { khqf, khqqf, znqf },
36
+  data() {
37
+    return {
38
+      type: "khqf",
39
+    };
40
+  },
41
+  methods: {
42
+    changeType(type) {
43
+      this.type = type;
44
+    },
45
+  },
46
+};
47
+</script>

+ 370 - 0
project/src/components/dataBoard/sendData/khqf.vue

@@ -0,0 +1,370 @@
1
+<template>
2
+  <div v-loading="loading">
3
+    <div class="screenBox flex-align-center">
4
+      <div class="flex-align-center" style="flex: 1">
5
+        <datePicker
6
+          title="自定义"
7
+          :is_include_today="false"
8
+          :pickerOptions="pickerOptions"
9
+          :quickFlag="true"
10
+          :afferent_time="default_time"
11
+          :clearFlag="false"
12
+          @changeTime="changeTime"
13
+        />
14
+        <selfChannel
15
+          title="创建人"
16
+          type="circleCreate"
17
+          :labelWidth="true"
18
+          @channelDefine="onChangeCreate"
19
+        />
20
+        <selfInputV2
21
+          v-model="keyword"
22
+          label_name="标题"
23
+          placeholder="请输入关键字"
24
+          labelWidth
25
+          @change="onChangeKeyword"
26
+        />
27
+      </div>
28
+      <div class="right">
29
+        <el-button
30
+          v-if="isCanExport"
31
+          type="primary"
32
+          size="mini"
33
+          @click="init(1, 'export')"
34
+          >导出Excel</el-button
35
+        >
36
+      </div>
37
+    </div>
38
+    <div class="dataInfoBox">
39
+      <div
40
+        class="dataInfoItem"
41
+        v-for="(item, index) in dataInfoArrs"
42
+        :key="index"
43
+      >
44
+        <div class="dataItemTitle">
45
+          <img :src="item.icon" style="height: 14px" class="titleIcon" />
46
+          <span>{{ item.label }}</span>
47
+        </div>
48
+        <div class="dataItem-data">
49
+          {{
50
+            dataInfo && (dataInfo[item.prop] || dataInfo[item.prop] == 0)
51
+              ? $formatNum(dataInfo[item.prop])
52
+              : "-"
53
+          }}
54
+          <span class="f15">{{
55
+            item.brackets_prop
56
+              ? `(${
57
+                  dataInfo &&
58
+                  (dataInfo[item.brackets_prop] ||
59
+                    dataInfo[item.brackets_prop] == 0)
60
+                    ? $formatNum(dataInfo[item.brackets_prop])
61
+                    : "-"
62
+                })`
63
+              : ""
64
+          }}</span>
65
+        </div>
66
+      </div>
67
+    </div>
68
+    <div>
69
+      <ux-grid
70
+        ref="plxTable"
71
+        :border="false"
72
+        @row-click="
73
+          () => {
74
+            return;
75
+          }
76
+        "
77
+        :header-cell-style="
78
+          () => {
79
+            return {
80
+              backgroundColor: '#FFFFFF !important',
81
+              border: 'none!important',
82
+            };
83
+          }
84
+        "
85
+        :height="height"
86
+        show-footer-overflow="tooltip"
87
+        show-overflow="tooltip"
88
+        size="mini"
89
+      >
90
+        <ux-table-column
91
+          v-for="item in desCol"
92
+          v-if="!item.excelShow"
93
+          :key="item.prop"
94
+          :resizable="true"
95
+          :field="item.prop"
96
+          :title="item.label"
97
+          :min-width="item.min_width ? item.min_width : 120"
98
+          :fixed="item.fixed ? item.fixed : ''"
99
+          align="center"
100
+        >
101
+          <template #header>
102
+            <div :class="['flex-align-jus-center']">
103
+              {{ item.label
104
+              }}{{ item.brackets_label ? `(${item.brackets_label})` : "" }}
105
+              <el-tooltip
106
+                v-if="item.notes"
107
+                :content="item.notes"
108
+                placement="top"
109
+              >
110
+                <div><i class="el-icon-question"></i></div>
111
+              </el-tooltip>
112
+            </div>
113
+          </template>
114
+          <template v-slot="{ row }">
115
+            <span>{{
116
+              row[item.prop] !== "" &&
117
+              (row[item.prop] || row[item.prop] === 0 || row[item.prop] === "0")
118
+                ? $formatNum(row[item.prop])
119
+                : "-"
120
+            }}</span>
121
+            <span v-if="item.brackets_label"
122
+              >({{
123
+                row[item.brackets_prop] != "" &&
124
+                (row[item.brackets_prop] || row[item.brackets_prop] == 0)
125
+                  ? $formatNum(row[item.brackets_prop])
126
+                  : "-"
127
+              }})</span
128
+            >
129
+          </template>
130
+        </ux-table-column>
131
+      </ux-grid>
132
+      <div class="pagination" v-show="total > 0">
133
+        <el-pagination
134
+          background
135
+          :current-page="page"
136
+          @current-change="handleCurrentChange"
137
+          layout="prev, pager, next, sizes, jumper"
138
+          :page-sizes="[20, 50, 100]"
139
+          @size-change="handleSizeChange"
140
+          :page-count="Number(pages)"
141
+        />
142
+      </div>
143
+    </div>
144
+  </div>
145
+</template>
146
+<script>
147
+import datePicker from "@/components/assembly/screen/datePicker.vue";
148
+import selfChannel from "@/components/assembly/screen/channel.vue";
149
+import selfInputV2 from "@/components/assembly/screen/inputV2.vue";
150
+
151
+export default {
152
+  components: { selfChannel, datePicker, selfInputV2 },
153
+  data() {
154
+    return {
155
+      loading: false,
156
+      default_time: [this.$getDay(-8, false), this.$getDay(-1, false)],
157
+      pickerOptions: {
158
+        disabledDate(time) {
159
+          return time > Date.now() - 8.64e7;
160
+        },
161
+      },
162
+      height: "",
163
+      creator_id: "",
164
+      keyword: "",
165
+      time: [],
166
+      dataInfoArrs: [
167
+        {
168
+          prop: "send_total",
169
+          label: "预计发送人数",
170
+          icon: require("@/assets/img/iconNew/充值人数@2x.png"),
171
+        },
172
+        {
173
+          prop: "send_success",
174
+          label: "送达客户数",
175
+          icon: require("@/assets/img/icon/总关注人数@2x.png"),
176
+        },
177
+        {
178
+          prop: "send_fail",
179
+          label: "失败客户数",
180
+          icon: require("@/assets/img/iconNew/总充值人数@2x.png"),
181
+        },
182
+        {
183
+          prop: "send_fail_user",
184
+          label: "失败客服数",
185
+          icon: require("@/assets/img/iconNew/总充值人数@2x.png"),
186
+        },
187
+      ],
188
+      dataInfo: {},
189
+      desCol: [
190
+        { prop: "name", label: "群发标题", fixed: "left", min_width: "160" },
191
+        { prop: "send_time", label: "发送时间", min_width: "160" },
192
+        { prop: "creator", label: "创建人" },
193
+        { prop: "send_total", label: "预计送达人数" },
194
+        { prop: "send_success", label: "送达客户数" },
195
+        { prop: "send_fail", label: "失败客户数" },
196
+        { prop: "send_fail_user", label: "失败客服数" },
197
+      ],
198
+      page: 1,
199
+      pages: 0,
200
+      total: 0,
201
+      page_size: 20,
202
+    };
203
+  },
204
+  computed: {
205
+    // 是否有“导出”权限
206
+    isCanExport() {
207
+      return !!this.$store.state.dataBoardAuth.can_export;
208
+    },
209
+  },
210
+  created() {
211
+    this.time = this.default_time;
212
+    this.height =
213
+      document.documentElement.clientHeight - 400 > 400
214
+        ? document.documentElement.clientHeight - 400
215
+        : 400;
216
+    this.init(1);
217
+  },
218
+  methods: {
219
+    changeTime(time) {
220
+      //筛选时间变化
221
+      if (!time || (time && time.length == 0)) {
222
+        this.time = [];
223
+      } else {
224
+        this.time = time;
225
+      }
226
+      this.init(1);
227
+    },
228
+    init(page, type) {
229
+      if (type != "export") {
230
+        this.page = page ? page : this.page;
231
+      } else {
232
+        if (this.total == 0) {
233
+          this.$message({
234
+            message: "暂无数据可导出",
235
+            type: "warning",
236
+          });
237
+          return;
238
+        }
239
+      }
240
+      this.loading = true;
241
+      this.$axios
242
+        .get(this.URL.BASEURL + this.URL.sendData_massMsg, {
243
+          params: {
244
+            send_time_start: this.time[0],
245
+            send_time_end: this.time[1],
246
+            creator_id: this.creator_id,
247
+            keyword: this.keyword,
248
+            page: type == "export" ? 1 : this.page,
249
+            page_size:
250
+              type == "export"
251
+                ? this.$store.state.exportNumber
252
+                : this.page_size,
253
+          },
254
+        })
255
+        .then((res) => {
256
+          var res = res.data;
257
+          this.loading = false;
258
+          if (res && res.errno == 0) {
259
+            console.log("res => ", res);
260
+            if (res.rst.data.total) {
261
+              // 汇总数据
262
+              this.dataInfo = res.rst.data.total;
263
+            }
264
+            if (type == "export") {
265
+              // 列表导出
266
+              this.exportEvent(res.rst.data.list);
267
+            } else {
268
+              // 列表数据
269
+              this.datas = res.rst.data.list;
270
+              this.$refs.plxTable && this.$refs.plxTable.reloadData(this.datas);
271
+              this.total = res.rst.pageInfo.total;
272
+              this.pages = res.rst.pageInfo.pages;
273
+            }
274
+          } else if (res.errno != 4002) {
275
+            this.$message({
276
+              message: res.err,
277
+              type: "warning",
278
+            });
279
+          }
280
+        })
281
+        .catch((err) => {
282
+          this.loading = false;
283
+        });
284
+    },
285
+    handleCurrentChange(val) {
286
+      this.init(val);
287
+    },
288
+    handleSizeChange(page_size) {
289
+      this.page_size = page_size;
290
+      this.init(1);
291
+    },
292
+    exportEvent(data) {
293
+      let list = data;
294
+      let tHeader = this.desCol.map((v) => {
295
+        return v.label;
296
+      });
297
+      let filterVal = this.desCol.map((v) => {
298
+        return v.prop;
299
+      });
300
+      let excelDatas = [
301
+        {
302
+          tHeader: tHeader, // sheet表一头部
303
+          filterVal: filterVal, // 表一的数据字段
304
+          tableDatas: list, // 表一的整体json数据
305
+          sheetName: "", // 表一的sheet名字
306
+        },
307
+      ];
308
+      this.$exportOrder({
309
+        excelDatas,
310
+        name: `送达数据-客户群发(导出时间:${this.$getDay(0)})`,
311
+      });
312
+    },
313
+    onChangeCreate(val) {
314
+      this.creator_id = val;
315
+      this.init(1);
316
+    },
317
+    onChangeKeyword(val) {
318
+      this.keyword = val;
319
+      this.init(1);
320
+    },
321
+  },
322
+};
323
+</script>
324
+<style lang="scss" scoped>
325
+.screenBox {
326
+  background: #fff;
327
+  padding: 5px 20px;
328
+}
329
+
330
+.dataInfoBox {
331
+  display: flex;
332
+  margin-top: 10px;
333
+  flex-wrap: wrap;
334
+
335
+  .dataInfoItem {
336
+    background: #ffffff;
337
+    border-radius: 8px;
338
+    margin-right: 10px;
339
+    margin-bottom: 10px;
340
+    padding: 0 19px;
341
+    height: 70px;
342
+    display: flex;
343
+    flex-direction: column;
344
+    justify-content: center;
345
+    align-items: center;
346
+
347
+    .dataItemTitle {
348
+      display: flex;
349
+      align-items: center;
350
+      color: #6f6f6f;
351
+      font-size: 13px;
352
+      line-height: 17px;
353
+      font-weight: bold;
354
+
355
+      .titleIcon {
356
+        height: 16px;
357
+        margin-right: 4px;
358
+      }
359
+    }
360
+
361
+    .dataItem-data {
362
+      color: #000000;
363
+      font-size: 19px;
364
+      line-height: 28px;
365
+      font-weight: bold;
366
+      margin-top: 2px;
367
+    }
368
+  }
369
+}
370
+</style>

+ 371 - 0
project/src/components/dataBoard/sendData/khqqf.vue

@@ -0,0 +1,371 @@
1
+<template>
2
+  <div v-loading="loading">
3
+    <div class="screenBox flex-align-center">
4
+      <div class="flex-align-center" style="flex: 1">
5
+        <datePicker
6
+          title="自定义"
7
+          :is_include_today="false"
8
+          :pickerOptions="pickerOptions"
9
+          :quickFlag="true"
10
+          :afferent_time="default_time"
11
+          :clearFlag="false"
12
+          @changeTime="changeTime"
13
+        />
14
+        <selfChannel
15
+          title="创建人"
16
+          type="circleCreate"
17
+          :labelWidth="true"
18
+          @channelDefine="onChangeCreate"
19
+        />
20
+        <selfInputV2
21
+          v-model="keyword"
22
+          label_name="标题"
23
+          placeholder="请输入关键字"
24
+          labelWidth
25
+          @change="onChangeKeyword"
26
+        />
27
+      </div>
28
+      <div class="right">
29
+        <el-button
30
+          v-if="isCanExport"
31
+          type="primary"
32
+          size="mini"
33
+          @click="init(1, 'export')"
34
+          >导出Excel</el-button
35
+        >
36
+      </div>
37
+    </div>
38
+    <div class="dataInfoBox">
39
+      <div
40
+        class="dataInfoItem"
41
+        v-for="(item, index) in dataInfoArrs"
42
+        :key="index"
43
+      >
44
+        <div class="dataItemTitle">
45
+          <img :src="item.icon" style="height: 14px" class="titleIcon" />
46
+          <span>{{ item.label }}</span>
47
+        </div>
48
+        <div class="dataItem-data">
49
+          {{
50
+            dataInfo && (dataInfo[item.prop] || dataInfo[item.prop] == 0)
51
+              ? $formatNum(dataInfo[item.prop])
52
+              : "-"
53
+          }}
54
+          <span class="f15">{{
55
+            item.brackets_prop
56
+              ? `(${
57
+                  dataInfo &&
58
+                  (dataInfo[item.brackets_prop] ||
59
+                    dataInfo[item.brackets_prop] == 0)
60
+                    ? $formatNum(dataInfo[item.brackets_prop])
61
+                    : "-"
62
+                })`
63
+              : ""
64
+          }}</span>
65
+        </div>
66
+      </div>
67
+    </div>
68
+    <div>
69
+      <ux-grid
70
+        ref="plxTable"
71
+        :border="false"
72
+        @row-click="
73
+          () => {
74
+            return;
75
+          }
76
+        "
77
+        :header-cell-style="
78
+          () => {
79
+            return {
80
+              backgroundColor: '#FFFFFF !important',
81
+              border: 'none!important',
82
+            };
83
+          }
84
+        "
85
+        :height="height"
86
+        show-footer-overflow="tooltip"
87
+        show-overflow="tooltip"
88
+        size="mini"
89
+      >
90
+        <ux-table-column
91
+          v-for="item in desCol"
92
+          v-if="!item.excelShow"
93
+          :key="item.prop"
94
+          :resizable="true"
95
+          :field="item.prop"
96
+          :title="item.label"
97
+          :min-width="item.min_width ? item.min_width : 120"
98
+          :fixed="item.fixed ? item.fixed : ''"
99
+          align="center"
100
+        >
101
+          <template #header>
102
+            <div :class="['flex-align-jus-center']">
103
+              {{ item.label
104
+              }}{{ item.brackets_label ? `(${item.brackets_label})` : "" }}
105
+              <el-tooltip
106
+                v-if="item.notes"
107
+                :content="item.notes"
108
+                placement="top"
109
+              >
110
+                <div><i class="el-icon-question"></i></div>
111
+              </el-tooltip>
112
+            </div>
113
+          </template>
114
+          <template v-slot="{ row }">
115
+            <span>{{
116
+              row[item.prop] !== "" &&
117
+              (row[item.prop] || row[item.prop] === 0 || row[item.prop] === "0")
118
+                ? $formatNum(row[item.prop])
119
+                : "-"
120
+            }}</span>
121
+            <span v-if="item.brackets_label"
122
+              >({{
123
+                row[item.brackets_prop] != "" &&
124
+                (row[item.brackets_prop] || row[item.brackets_prop] == 0)
125
+                  ? $formatNum(row[item.brackets_prop])
126
+                  : "-"
127
+              }})</span
128
+            >
129
+          </template>
130
+        </ux-table-column>
131
+      </ux-grid>
132
+      <div class="pagination" v-show="total > 0">
133
+        <el-pagination
134
+          background
135
+          :current-page="page"
136
+          @current-change="handleCurrentChange"
137
+          layout="prev, pager, next, sizes, jumper"
138
+          :page-sizes="[20, 50, 100]"
139
+          @size-change="handleSizeChange"
140
+          :page-count="Number(pages)"
141
+        />
142
+      </div>
143
+    </div>
144
+  </div>
145
+</template>
146
+<script>
147
+import datePicker from "@/components/assembly/screen/datePicker.vue";
148
+import selfChannel from "@/components/assembly/screen/channel.vue";
149
+import selfInputV2 from "@/components/assembly/screen/inputV2.vue";
150
+
151
+export default {
152
+  components: { selfChannel, datePicker, selfInputV2 },
153
+  data() {
154
+    return {
155
+      loading: false,
156
+      default_time: [this.$getDay(-8, false), this.$getDay(-1, false)],
157
+      pickerOptions: {
158
+        disabledDate(time) {
159
+          return time > Date.now() - 8.64e7;
160
+        },
161
+      },
162
+      height: "",
163
+      creator_id: "",
164
+      keyword: "",
165
+      time: [],
166
+      dataInfoArrs: [
167
+        {
168
+          prop: "send_user_success",
169
+          label: "已发送群主数",
170
+          icon: require("@/assets/img/iconNew/充值人数@2x.png"),
171
+        },
172
+        {
173
+          prop: "send_user_fail",
174
+          label: "未发送群主数",
175
+          icon: require("@/assets/img/iconNew/总充值人数@2x.png"),
176
+        },
177
+        {
178
+          prop: "send_chat_success",
179
+          label: "送达群聊数",
180
+          icon: require("@/assets/img/icon/总关注人数@2x.png"),
181
+        },
182
+        {
183
+          prop: "send_chat_fail",
184
+          label: "未送达群聊数",
185
+          icon: require("@/assets/img/iconNew/总充值人数@2x.png"),
186
+        },
187
+      ],
188
+      dataInfo: {},
189
+      desCol: [
190
+        { prop: "name", label: "群发标题", fixed: "left", min_width: "160" },
191
+        { prop: "send_time", label: "发送时间", min_width: "160" },
192
+        { prop: "creator", label: "创建人" },
193
+        { prop: "send_user_success", label: "已发送群主数" },
194
+        { prop: "send_user_fail", label: "未发送群主数" },
195
+        { prop: "send_chat_success", label: "送达群聊数" },
196
+        { prop: "send_chat_fail", label: "未送达群聊数" },
197
+        { prop: "member_count", label: "送达群聊人数" },
198
+      ],
199
+      page: 1,
200
+      pages: 0,
201
+      total: 0,
202
+      page_size: 20,
203
+    };
204
+  },
205
+  computed: {
206
+    // 是否有“导出”权限
207
+    isCanExport() {
208
+      return !!this.$store.state.dataBoardAuth.can_export;
209
+    },
210
+  },
211
+  created() {
212
+    this.time = this.default_time;
213
+    this.height =
214
+      document.documentElement.clientHeight - 400 > 400
215
+        ? document.documentElement.clientHeight - 400
216
+        : 400;
217
+    this.init(1);
218
+  },
219
+  methods: {
220
+    changeTime(time) {
221
+      //筛选时间变化
222
+      if (!time || (time && time.length == 0)) {
223
+        this.time = [];
224
+      } else {
225
+        this.time = time;
226
+      }
227
+      this.init(1);
228
+    },
229
+    init(page, type) {
230
+      if (type != "export") {
231
+        this.page = page ? page : this.page;
232
+      } else {
233
+        if (this.total == 0) {
234
+          this.$message({
235
+            message: "暂无数据可导出",
236
+            type: "warning",
237
+          });
238
+          return;
239
+        }
240
+      }
241
+      this.loading = true;
242
+      this.$axios
243
+        .get(this.URL.BASEURL + this.URL.sendData_chatGroupMassMsg, {
244
+          params: {
245
+            send_time_start: this.time[0],
246
+            send_time_end: this.time[1],
247
+            creator_id: this.creator_id,
248
+            keyword: this.keyword,
249
+            page: type == "export" ? 1 : this.page,
250
+            page_size:
251
+              type == "export"
252
+                ? this.$store.state.exportNumber
253
+                : this.page_size,
254
+          },
255
+        })
256
+        .then((res) => {
257
+          var res = res.data;
258
+          this.loading = false;
259
+          if (res && res.errno == 0) {
260
+            console.log("res => ", res);
261
+            if (res.rst.data.total) {
262
+              // 汇总数据
263
+              this.dataInfo = res.rst.data.total;
264
+            }
265
+            if (type == "export") {
266
+              // 列表导出
267
+              this.exportEvent(res.rst.data.list);
268
+            } else {
269
+              // 列表数据
270
+              this.datas = res.rst.data.list;
271
+              this.$refs.plxTable && this.$refs.plxTable.reloadData(this.datas);
272
+              this.total = res.rst.pageInfo.total;
273
+              this.pages = res.rst.pageInfo.pages;
274
+            }
275
+          } else if (res.errno != 4002) {
276
+            this.$message({
277
+              message: res.err,
278
+              type: "warning",
279
+            });
280
+          }
281
+        })
282
+        .catch((err) => {
283
+          this.loading = false;
284
+        });
285
+    },
286
+    handleCurrentChange(val) {
287
+      this.init(val);
288
+    },
289
+    handleSizeChange(page_size) {
290
+      this.page_size = page_size;
291
+      this.init(1);
292
+    },
293
+    exportEvent(data) {
294
+      let list = data;
295
+      let tHeader = this.desCol.map((v) => {
296
+        return v.label;
297
+      });
298
+      let filterVal = this.desCol.map((v) => {
299
+        return v.prop;
300
+      });
301
+      let excelDatas = [
302
+        {
303
+          tHeader: tHeader, // sheet表一头部
304
+          filterVal: filterVal, // 表一的数据字段
305
+          tableDatas: list, // 表一的整体json数据
306
+          sheetName: "", // 表一的sheet名字
307
+        },
308
+      ];
309
+      this.$exportOrder({
310
+        excelDatas,
311
+        name: `送达数据-客户群群发(导出时间:${this.$getDay(0)})`,
312
+      });
313
+    },
314
+    onChangeCreate(val) {
315
+      this.creator_id = val;
316
+      this.init(1);
317
+    },
318
+    onChangeKeyword(val) {
319
+      this.keyword = val;
320
+      this.init(1);
321
+    },
322
+  },
323
+};
324
+</script>
325
+<style lang="scss" scoped>
326
+.screenBox {
327
+  background: #fff;
328
+  padding: 5px 20px;
329
+}
330
+
331
+.dataInfoBox {
332
+  display: flex;
333
+  margin-top: 10px;
334
+  flex-wrap: wrap;
335
+
336
+  .dataInfoItem {
337
+    background: #ffffff;
338
+    border-radius: 8px;
339
+    margin-right: 10px;
340
+    margin-bottom: 10px;
341
+    padding: 0 19px;
342
+    height: 70px;
343
+    display: flex;
344
+    flex-direction: column;
345
+    justify-content: center;
346
+    align-items: center;
347
+
348
+    .dataItemTitle {
349
+      display: flex;
350
+      align-items: center;
351
+      color: #6f6f6f;
352
+      font-size: 13px;
353
+      line-height: 17px;
354
+      font-weight: bold;
355
+
356
+      .titleIcon {
357
+        height: 16px;
358
+        margin-right: 4px;
359
+      }
360
+    }
361
+
362
+    .dataItem-data {
363
+      color: #000000;
364
+      font-size: 19px;
365
+      line-height: 28px;
366
+      font-weight: bold;
367
+      margin-top: 2px;
368
+    }
369
+  }
370
+}
371
+</style>

+ 359 - 0
project/src/components/dataBoard/sendData/znqf.vue

@@ -0,0 +1,359 @@
1
+<template>
2
+  <div v-loading="loading">
3
+    <div class="screenBox flex-align-center">
4
+      <div class="flex-align-center" style="flex: 1">
5
+        <datePicker
6
+          title="自定义"
7
+          :is_include_today="false"
8
+          :pickerOptions="pickerOptions"
9
+          :quickFlag="true"
10
+          :afferent_time="default_time"
11
+          :clearFlag="false"
12
+          @changeTime="changeTime"
13
+        />
14
+        <selfChannel
15
+          title="创建人"
16
+          type="circleCreate"
17
+          :labelWidth="true"
18
+          @channelDefine="onChangeCreate"
19
+        />
20
+        <selfInputV2
21
+          v-model="keyword"
22
+          label_name="标题"
23
+          placeholder="请输入关键字"
24
+          labelWidth
25
+          @change="onChangeKeyword"
26
+        />
27
+      </div>
28
+      <div class="right">
29
+        <el-button
30
+          v-if="isCanExport"
31
+          type="primary"
32
+          size="mini"
33
+          @click="init(1, 'export')"
34
+          >导出Excel</el-button
35
+        >
36
+      </div>
37
+    </div>
38
+    <div class="dataInfoBox">
39
+      <div
40
+        class="dataInfoItem"
41
+        v-for="(item, index) in dataInfoArrs"
42
+        :key="index"
43
+      >
44
+        <div class="dataItemTitle">
45
+          <img :src="item.icon" style="height: 14px" class="titleIcon" />
46
+          <span>{{ item.label }}</span>
47
+        </div>
48
+        <div class="dataItem-data">
49
+          {{
50
+            dataInfo && (dataInfo[item.prop] || dataInfo[item.prop] == 0)
51
+              ? $formatNum(dataInfo[item.prop])
52
+              : "-"
53
+          }}
54
+          <span class="f15">{{
55
+            item.brackets_prop
56
+              ? `(${
57
+                  dataInfo &&
58
+                  (dataInfo[item.brackets_prop] ||
59
+                    dataInfo[item.brackets_prop] == 0)
60
+                    ? $formatNum(dataInfo[item.brackets_prop])
61
+                    : "-"
62
+                })`
63
+              : ""
64
+          }}</span>
65
+        </div>
66
+      </div>
67
+    </div>
68
+    <div>
69
+      <ux-grid
70
+        ref="plxTable"
71
+        :border="false"
72
+        @row-click="
73
+          () => {
74
+            return;
75
+          }
76
+        "
77
+        :header-cell-style="
78
+          () => {
79
+            return {
80
+              backgroundColor: '#FFFFFF !important',
81
+              border: 'none!important',
82
+            };
83
+          }
84
+        "
85
+        :height="height"
86
+        show-footer-overflow="tooltip"
87
+        show-overflow="tooltip"
88
+        size="mini"
89
+      >
90
+        <ux-table-column
91
+          v-for="item in desCol"
92
+          v-if="!item.excelShow"
93
+          :key="item.prop"
94
+          :resizable="true"
95
+          :field="item.prop"
96
+          :title="item.label"
97
+          :min-width="item.min_width ? item.min_width : 120"
98
+          :fixed="item.fixed ? item.fixed : ''"
99
+          align="center"
100
+        >
101
+          <template #header>
102
+            <div :class="['flex-align-jus-center']">
103
+              {{ item.label
104
+              }}{{ item.brackets_label ? `(${item.brackets_label})` : "" }}
105
+              <el-tooltip
106
+                v-if="item.notes"
107
+                :content="item.notes"
108
+                placement="top"
109
+              >
110
+                <div><i class="el-icon-question"></i></div>
111
+              </el-tooltip>
112
+            </div>
113
+          </template>
114
+          <template v-slot="{ row }">
115
+            <span>{{
116
+              row[item.prop] !== "" &&
117
+              (row[item.prop] || row[item.prop] === 0 || row[item.prop] === "0")
118
+                ? $formatNum(row[item.prop])
119
+                : "-"
120
+            }}</span>
121
+            <span v-if="item.brackets_label"
122
+              >({{
123
+                row[item.brackets_prop] != "" &&
124
+                (row[item.brackets_prop] || row[item.brackets_prop] == 0)
125
+                  ? $formatNum(row[item.brackets_prop])
126
+                  : "-"
127
+              }})</span
128
+            >
129
+          </template>
130
+        </ux-table-column>
131
+      </ux-grid>
132
+      <div class="pagination" v-show="total > 0">
133
+        <el-pagination
134
+          background
135
+          :current-page="page"
136
+          @current-change="handleCurrentChange"
137
+          layout="prev, pager, next, sizes, jumper"
138
+          :page-sizes="[20, 50, 100]"
139
+          @size-change="handleSizeChange"
140
+          :page-count="Number(pages)"
141
+        />
142
+      </div>
143
+    </div>
144
+  </div>
145
+</template>
146
+<script>
147
+import datePicker from "@/components/assembly/screen/datePicker.vue";
148
+import selfChannel from "@/components/assembly/screen/channel.vue";
149
+import selfInputV2 from "@/components/assembly/screen/inputV2.vue";
150
+
151
+export default {
152
+  components: { selfChannel, datePicker, selfInputV2 },
153
+  data() {
154
+    return {
155
+      loading: false,
156
+      default_time: [this.$getDay(-8, false), this.$getDay(-1, false)],
157
+      pickerOptions: {
158
+        disabledDate(time) {
159
+          return time > Date.now() - 8.64e7;
160
+        },
161
+      },
162
+      height: "",
163
+      creator_id: "",
164
+      keyword: "",
165
+      time: [],
166
+      dataInfoArrs: [
167
+        {
168
+          prop: "send_success",
169
+          label: "已送达客户数",
170
+          icon: require("@/assets/img/iconNew/充值人数@2x.png"),
171
+        },
172
+        {
173
+          prop: "send_fail",
174
+          label: "失败客户数",
175
+          icon: require("@/assets/img/iconNew/总充值人数@2x.png"),
176
+        },
177
+      ],
178
+      dataInfo: {},
179
+      desCol: [
180
+        { prop: "group_name", label: "分组名称", fixed: "left", min_width: "160" },
181
+        { prop: "name", label: "sop链接标题", min_width: "160" },
182
+        { prop: "create_time", label: "发送时间", min_width: "160" },
183
+        { prop: "creator", label: "创建人" },
184
+        { prop: "send_success", label: "送达客户数" },
185
+        { prop: "send_fail", label: "失败客户数" },
186
+      ],
187
+      page: 1,
188
+      pages: 0,
189
+      total: 0,
190
+      page_size: 20,
191
+    };
192
+  },
193
+  computed: {
194
+    // 是否有“导出”权限
195
+    isCanExport() {
196
+      return !!this.$store.state.dataBoardAuth.can_export;
197
+    },
198
+  },
199
+  created() {
200
+    this.time = this.default_time;
201
+    this.height =
202
+      document.documentElement.clientHeight - 400 > 400
203
+        ? document.documentElement.clientHeight - 400
204
+        : 400;
205
+    this.init(1);
206
+  },
207
+  methods: {
208
+    changeTime(time) {
209
+      //筛选时间变化
210
+      if (!time || (time && time.length == 0)) {
211
+        this.time = [];
212
+      } else {
213
+        this.time = time;
214
+      }
215
+      this.init(1);
216
+    },
217
+    init(page, type) {
218
+      if (type != "export") {
219
+        this.page = page ? page : this.page;
220
+      } else {
221
+        if (this.total == 0) {
222
+          this.$message({
223
+            message: "暂无数据可导出",
224
+            type: "warning",
225
+          });
226
+          return;
227
+        }
228
+      }
229
+      this.loading = true;
230
+      this.$axios
231
+        .get(this.URL.BASEURL + this.URL.sendData_PeriodMassMsg, {
232
+          params: {
233
+            send_time_start: this.time[0],
234
+            send_time_end: this.time[1],
235
+            creator_id: this.creator_id,
236
+            keyword: this.keyword,
237
+            page: type == "export" ? 1 : this.page,
238
+            page_size:
239
+              type == "export"
240
+                ? this.$store.state.exportNumber
241
+                : this.page_size,
242
+          },
243
+        })
244
+        .then((res) => {
245
+          var res = res.data;
246
+          this.loading = false;
247
+          if (res && res.errno == 0) {
248
+            console.log("res => ", res);
249
+            if (res.rst.data.total) {
250
+              // 汇总数据
251
+              this.dataInfo = res.rst.data.total;
252
+            }
253
+            if (type == "export") {
254
+              // 列表导出
255
+              this.exportEvent(res.rst.data.list);
256
+            } else {
257
+              // 列表数据
258
+              this.datas = res.rst.data.list;
259
+              this.$refs.plxTable && this.$refs.plxTable.reloadData(this.datas);
260
+              this.total = res.rst.pageInfo.total;
261
+              this.pages = res.rst.pageInfo.pages;
262
+            }
263
+          } else if (res.errno != 4002) {
264
+            this.$message({
265
+              message: res.err,
266
+              type: "warning",
267
+            });
268
+          }
269
+        })
270
+        .catch((err) => {
271
+          this.loading = false;
272
+        });
273
+    },
274
+    handleCurrentChange(val) {
275
+      this.init(val);
276
+    },
277
+    handleSizeChange(page_size) {
278
+      this.page_size = page_size;
279
+      this.init(1);
280
+    },
281
+    exportEvent(data) {
282
+      let list = data;
283
+      let tHeader = this.desCol.map((v) => {
284
+        return v.label;
285
+      });
286
+      let filterVal = this.desCol.map((v) => {
287
+        return v.prop;
288
+      });
289
+      let excelDatas = [
290
+        {
291
+          tHeader: tHeader, // sheet表一头部
292
+          filterVal: filterVal, // 表一的数据字段
293
+          tableDatas: list, // 表一的整体json数据
294
+          sheetName: "", // 表一的sheet名字
295
+        },
296
+      ];
297
+      this.$exportOrder({
298
+        excelDatas,
299
+        name: `送达数据-智能群发(导出时间:${this.$getDay(0)})`,
300
+      });
301
+    },
302
+    onChangeCreate(val) {
303
+      this.creator_id = val;
304
+      this.init(1);
305
+    },
306
+    onChangeKeyword(val) {
307
+      this.keyword = val;
308
+      this.init(1);
309
+    },
310
+  },
311
+};
312
+</script>
313
+<style lang="scss" scoped>
314
+.screenBox {
315
+  background: #fff;
316
+  padding: 5px 20px;
317
+}
318
+
319
+.dataInfoBox {
320
+  display: flex;
321
+  margin-top: 10px;
322
+  flex-wrap: wrap;
323
+
324
+  .dataInfoItem {
325
+    background: #ffffff;
326
+    border-radius: 8px;
327
+    margin-right: 10px;
328
+    margin-bottom: 10px;
329
+    padding: 0 19px;
330
+    height: 70px;
331
+    display: flex;
332
+    flex-direction: column;
333
+    justify-content: center;
334
+    align-items: center;
335
+
336
+    .dataItemTitle {
337
+      display: flex;
338
+      align-items: center;
339
+      color: #6f6f6f;
340
+      font-size: 13px;
341
+      line-height: 17px;
342
+      font-weight: bold;
343
+
344
+      .titleIcon {
345
+        height: 16px;
346
+        margin-right: 4px;
347
+      }
348
+    }
349
+
350
+    .dataItem-data {
351
+      color: #000000;
352
+      font-size: 19px;
353
+      line-height: 28px;
354
+      font-weight: bold;
355
+      margin-top: 2px;
356
+    }
357
+  }
358
+}
359
+</style>

+ 12 - 0
project/src/router/allRouter.js

@@ -44,6 +44,7 @@ const thePublic = () => import(/* webpackChunkName: 'thePublic' */ '@/components
44 44
 const playletData = () => import(/* webpackChunkName: 'playletData' */ '@/components/dataBoard/playletData.vue')
45 45
 const operateDayRetrieve = () => import(/* webpackChunkName: 'operateDayRetrieve' */ '@/components/dataBoard/operateDayRetrieve.vue')
46 46
 const populariz = () => import(/* webpackChunkName: 'populariz' */ '@/components/dataBoard/populariz/index.vue')
47
+const sendData = () => import(/* webpackChunkName: 'sendData' */ '@/components/dataBoard/sendData/index.vue')
47 48
 const throwPerson = () => import(/* webpackChunkName: 'throwPerson' */ '@/components/dataBoard/throwPerson/index.vue')
48 49
 const regRangeReport = () => import(/* webpackChunkName: 'regRangeReport' */ '@/components/dataBoard/regRangeReport.vue')
49 50
 const regRangeReportHS = () => import(/* webpackChunkName: 'regRangeReportHS' */ '@/components/dataBoard/regRangeReportHS.vue')
@@ -888,6 +889,17 @@ export var allRouter = [
888 889
         }
889 890
       },
890 891
       {
892
+        path: 'sendData',
893
+        name: 'sendData',
894
+        component: sendData,
895
+        meta: {
896
+          keepAlive: false,
897
+          isLogin: true,
898
+          title: '群发送达数据',
899
+          isData: true
900
+        }
901
+      },
902
+      {
891 903
         path: 'throwPerson',
892 904
         name: 'throwPerson',
893 905
         component: throwPerson,