feat: 圈子数据统计对接

This commit is contained in:
kaizheng(郑凯) 2025-02-15 20:10:05 +08:00
parent 7cf28d106b
commit a5cbfa84a5
13 changed files with 928 additions and 242 deletions

View File

@ -58,6 +58,7 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"normalize.css": "7.0.0", "normalize.css": "7.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"numeral": "^2.0.6",
"path-to-regexp": "8.0.0", "path-to-regexp": "8.0.0",
"qrcodejs2": "^0.0.2", "qrcodejs2": "^0.0.2",
"qs": "^6.10.1", "qs": "^6.10.1",
@ -76,7 +77,7 @@
"vuex": "3.1.0", "vuex": "3.1.0",
"wangeditor": "^4.7.11", "wangeditor": "^4.7.11",
"webpack": "^4.47.0", "webpack": "^4.47.0",
"xlsx": "^0.18.5" "xlsx": "^0.14.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/parser": "^7.7.4", "@babel/parser": "^7.7.4",

View File

@ -143,3 +143,13 @@ export function setNotice(data) {
data data
}); });
} }
// 后台查询交易圈统计
export function getCircleStatistics(data) {
debugger;
return request({
url: "/admin/group/collect/query",
method: "post",
data
});
}

View File

@ -6,6 +6,7 @@ import "normalize.css/normalize.css";
import Element from "element-ui"; import Element from "element-ui";
import Vue2Editor from "vue2-editor"; import Vue2Editor from "vue2-editor";
import "@/utils/filters";
// 数据字典 // 数据字典
import dict from "./components/Dict"; import dict from "./components/Dict";

View File

@ -1,25 +1,30 @@
/* eslint-disable */ /* eslint-disable */
require('script-loader!file-saver'); require("script-loader!file-saver");
import XLSX from 'xlsx' import * as XLSX from "xlsx";
function generateArray(table) { function generateArray(table) {
var out = []; var out = [];
var rows = table.querySelectorAll('tr'); var rows = table.querySelectorAll("tr");
var ranges = []; var ranges = [];
for (var R = 0; R < rows.length; ++R) { for (var R = 0; R < rows.length; ++R) {
var outRow = []; var outRow = [];
var row = rows[R]; var row = rows[R];
var columns = row.querySelectorAll('td'); var columns = row.querySelectorAll("td");
for (var C = 0; C < columns.length; ++C) { for (var C = 0; C < columns.length; ++C) {
var cell = columns[C]; var cell = columns[C];
var colspan = cell.getAttribute('colspan'); var colspan = cell.getAttribute("colspan");
var rowspan = cell.getAttribute('rowspan'); var rowspan = cell.getAttribute("rowspan");
var cellValue = cell.innerText; var cellValue = cell.innerText;
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
//Skip ranges //Skip ranges
ranges.forEach(function(range) { ranges.forEach(function(range) {
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { if (
R >= range.s.r &&
R <= range.e.r &&
outRow.length >= range.s.c &&
outRow.length <= range.e.c
) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
} }
}); });
@ -38,19 +43,18 @@ function generateArray(table) {
c: outRow.length + colspan - 1 c: outRow.length + colspan - 1
} }
}); });
}; }
//Handle Value //Handle Value
outRow.push(cellValue !== "" ? cellValue : null); outRow.push(cellValue !== "" ? cellValue : null);
//Handle Colspan //Handle Colspan
if (colspan) if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
} }
out.push(outRow); out.push(outRow);
} }
return [out, ranges]; return [out, ranges];
}; }
function datenum(v, date1904) { function datenum(v, date1904) {
if (date1904) v += 1462; if (date1904) v += 1462;
@ -85,18 +89,18 @@ function sheet_from_array_of_arrays(data, opts) {
r: R r: R
}); });
if (typeof cell.v === 'number') cell.t = 'n'; if (typeof cell.v === "number") cell.t = "n";
else if (typeof cell.v === 'boolean') cell.t = 'b'; else if (typeof cell.v === "boolean") cell.t = "b";
else if (cell.v instanceof Date) { else if (cell.v instanceof Date) {
cell.t = 'n'; cell.t = "n";
cell.z = XLSX.SSF._table[14]; cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v); cell.v = datenum(cell.v);
} else cell.t = 's'; } else cell.t = "s";
ws[cell_ref] = cell; ws[cell_ref] = cell;
} }
} }
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
return ws; return ws;
} }
@ -109,7 +113,7 @@ function Workbook() {
function s2ab(s) { function s2ab(s) {
var buf = new ArrayBuffer(s.length); var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf); var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf; return buf;
} }
@ -127,21 +131,24 @@ export function export_table_to_excel(id) {
/* add ranges to worksheet */ /* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan']; // ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges; ws["!merges"] = ranges;
/* add worksheet to workbook */ /* add worksheet to workbook */
wb.SheetNames.push(ws_name); wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws; wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, { var wbout = XLSX.write(wb, {
bookType: 'xlsx', bookType: "xlsx",
bookSST: false, bookSST: false,
type: 'binary' type: "binary"
}); });
saveAs(new Blob([s2ab(wbout)], { saveAs(
new Blob([s2ab(wbout)], {
type: "application/octet-stream" type: "application/octet-stream"
}), "test.xlsx") }),
"test.xlsx"
);
} }
export function export_json_to_excel({ export function export_json_to_excel({
@ -149,11 +156,11 @@ export function export_json_to_excel({
data, data,
filename, filename,
autoWidth = true, autoWidth = true,
bookType= 'xlsx' bookType = "xlsx"
} = {}) { } = {}) {
/* original data */ /* original data */
filename = filename || 'excel-list' filename = filename || "excel-list";
data = [...data] data = [...data];
data.unshift(header); data.unshift(header);
var ws_name = "SheetJS"; var ws_name = "SheetJS";
var wb = new Workbook(), var wb = new Workbook(),
@ -161,34 +168,35 @@ export function export_json_to_excel({
if (autoWidth) { if (autoWidth) {
/*设置worksheet每列的最大宽度*/ /*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => { const colWidth = data.map(row =>
row.map(val => {
/*先判断是否为null/undefined*/ /*先判断是否为null/undefined*/
if (val == null) { if (val == null) {
return { return {
'wch': 10 wch: 10
}; };
} } else if (val.toString().charCodeAt(0) > 255) {
/*再判断是否为中文*/ /*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return { return {
'wch': val.toString().length * 2 wch: val.toString().length * 2
}; };
} else { } else {
return { return {
'wch': val.toString().length wch: val.toString().length
}; };
} }
})) })
);
/*以第一行为初始值*/ /*以第一行为初始值*/
let result = colWidth[0]; let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) { for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) { for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) { if (result[j]["wch"] < colWidth[i][j]["wch"]) {
result[j]['wch'] = colWidth[i][j]['wch']; result[j]["wch"] = colWidth[i][j]["wch"];
} }
} }
} }
ws['!cols'] = result; ws["!cols"] = result;
} }
/* add worksheet to workbook */ /* add worksheet to workbook */
@ -198,9 +206,12 @@ export function export_json_to_excel({
var wbout = XLSX.write(wb, { var wbout = XLSX.write(wb, {
bookType: bookType, bookType: bookType,
bookSST: false, bookSST: false,
type: 'binary' type: "binary"
}); });
saveAs(new Blob([s2ab(wbout)], { saveAs(
new Blob([s2ab(wbout)], {
type: "application/octet-stream" type: "application/octet-stream"
}), `${filename}.${bookType}`); }),
`${filename}.${bookType}`
);
} }

259
src/utils/filters.js Normal file
View File

@ -0,0 +1,259 @@
import Vue from "vue";
import numeral from "numeral";
import dayjs from "dayjs";
// 格式化和操作数字
Vue.filter("numberFormat", (value, format) => {
if (value == null) return "--";
return numeral(value || 0).format(format);
});
// 格式时间
Vue.filter("dateFilter", (value, format = "YYYY-MM-DD") => {
if (value == null) return "--";
return dayjs(value).format(format);
});
Vue.filter("getUserStatus", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "已启用";
case 2:
return "已禁用";
case 3:
return "已冻结";
}
});
Vue.filter("getRiskLevel", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "低风险";
case 2:
return "中低风险";
case 3:
return "中风险";
case 4:
return "中高风险";
case 5:
return "高风险";
}
});
Vue.filter("getProductType", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "观点包";
case 2:
return "观点";
case 7:
return "投资组合";
case 8:
return "股票池";
case 9:
return "套餐";
case 21:
return "增值产品";
case 22:
return "三方-课程";
case 23:
return "EFT专区";
case 24:
return "选股工具";
case 25:
return "小飞机理财";
case 31:
return "投资课堂";
case 32:
return "课程";
}
});
Vue.filter("getStatus", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "待提交";
case 2:
return "待一级审核";
case 11:
return "待二级审核";
case 12:
return "待三级审核";
case 3:
return "已上架";
case 4:
return "已驳回";
case 5:
return "已下架";
}
});
Vue.filter("getCommissionStatus", value => {
// if (value == null) return '--'
switch (value) {
case 0:
return "无信用账号";
case 1:
return "成功";
case -1:
return "失败";
case 2:
return "查询失败";
default:
return "--";
}
});
Vue.filter("getAuthType", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "公开可见";
case 2:
return "登录手机可见";
case 3:
return "登录资金账号可见";
case 4:
return "付费可见";
case 5:
return "仅供签约客户查看";
}
});
Vue.filter("getPeriodType", value => {
if (value == null) return "--";
switch (value) {
case 0:
return "免费永久,单品提拥";
case 7:
return "周";
case 14:
return "月";
case 16:
return "季度";
case 19:
return "半年";
case 20:
return "年";
case 99:
return "收费永久,单篇收费";
case 98:
return "收费永久,整体提拥";
}
});
Vue.filter("getCustomRiskLevel", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "C1保守型";
case 2:
return "C2谨慎型";
case 3:
return "C3稳健型";
case 4:
return "C4积极型";
case 5:
return "C5激进型";
}
});
Vue.filter("getPayType", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "支付宝";
case 5:
return "微信";
case 13:
return "提佣支付";
case 14:
return "保证金";
}
});
Vue.filter("getOrderStatus", (value, type) => {
if (value == null) return "--";
if (type === 13) {
switch (value) {
case 70:
return "签约失败";
case 95:
return "解约中";
case 100:
return "已解约";
case 180:
return "新订单";
case 220:
return "已签约";
}
} else {
switch (value) {
case 220:
return "支付成功";
case 70:
return "支付取消";
case 95:
return "退款中";
case 100:
return "退款成功";
case 180:
return "新订单";
case 80:
return "已过期";
case 90:
return "已关闭";
}
}
});
Vue.filter("getRefundStatus", (value, type) => {
if (value == null) return "--";
let mould = "";
if (type === 13) {
mould = "解约";
} else {
mould = "退款";
}
switch (value) {
case 180:
return `申请${mould}`;
case 200:
return `${mould}审核通过`;
case 210:
return `${mould}失败`;
case 220:
return `${mould}`;
}
});
Vue.filter("getChannel", value => {
if (value == null) return "--";
switch (value) {
case 1:
return "APP";
case 2:
return "小程序";
}
});
Vue.filter("formatPhone", phone => {
// console.log(phone)
// if(!phone) return phone
// if (typeof phone == 'number') {
// phone = phone.toString();
// }
// return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
return phone;
});
Vue.filter("formatRatio", (value, unit = true, num = 2) => {
if (value == null || value == "--") {
return "";
}
return (Number(value) * 100).toFixed(num) + (unit ? "%" : "");
});

View File

@ -27,7 +27,6 @@ export default {
} }
}, },
mounted() { mounted() {
this.initChart();
this.__resizeHandler = debounce(() => { this.__resizeHandler = debounce(() => {
if (this.chart) { if (this.chart) {
this.chart.resize(); this.chart.resize();
@ -44,11 +43,14 @@ export default {
this.chart = null; this.chart = null;
}, },
methods: { methods: {
initChart() { drawChart() {
if (this.chart) {
this.chart.setOption(this.option, true);
} else {
this.chart = echarts.init(this.$el, "macarons"); this.chart = echarts.init(this.$el, "macarons");
this.chart.setOption(this.option); this.chart.setOption(this.option);
} }
} }
}
}; };
</script> </script>

View File

@ -1,75 +1,129 @@
<template> <template>
<div class="module-content"> <div class="module-content">
<h4>成员</h4> <h4>内容</h4>
<ul class="data-list"> <ul class="data-list">
<li> <li>
<p>总动态</p> <p>今日老师发布内容</p>
<h5>100</h5> <h5>{{ currentData.advisorGroupContent }}</h5>
</li> </li>
<li> <li>
<p>今日新增动态书</p> <p>今日助教发布内容数</p>
<h5>500</h5> <h5>{{ currentData.assistantGroupContent }}</h5>
</li>
<li>
<p>今日用户互动内容数</p>
<h5>{{ currentData.customerGroupContent }}</h5>
</li>
<li>
<p>今日老师私聊内容数</p>
<h5>{{ currentData.advisorPrivateContent }}</h5>
</li>
<li>
<p>今日助教私聊内容数</p>
<h5>{{ currentData.assistantPrivateContent }}</h5>
</li>
<li>
<p>今日用户私聊内容数</p>
<h5>{{ currentData.customerPrivateContent }}</h5>
</li> </li>
</ul> </ul>
<div class="chart-header"> <div class="chart-header">
<h5>内容趋势</h5> <h5>内容趋势</h5>
<el-form :inline="true" size="mini" :model="formInline" class="my-form"> <el-form :inline="true" size="mini" :model="formInline" class="my-form">
<el-form-item label=""> <el-form-item label="" prop="timeType">
<el-select v-model="value" placeholder="请选择"> <el-select
<el-option v-model="timeType"
v-for="item in options" class="filter-item"
:key="item.value" style="width: 100px"
:label="item.label"
:value="item.value"
> >
</el-option> <el-option
v-for="item in [
{ label: '总数据', id: 1 },
{ label: '年', id: 2 },
{ label: '月', id: 3 },
{ label: '周', id: 4 },
{ label: '日', id: 5 }
]"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="" prop="username">
<el-date-picker <el-date-picker
v-model="value1" v-if="timeType === 2"
type="daterange" v-model="time"
range-separator="至" type="year"
start-placeholder="开始日期" placeholder="选择年"
end-placeholder="结束日期" value-format="yyyy-MM-dd HH:mm:ss"
> style="width: 100px"
</el-date-picker> />
<el-date-picker
v-else-if="timeType === 3"
v-model="time"
type="month"
placeholder="选择月"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100px"
/>
<el-date-picker
v-else-if="timeType === 4"
v-model="time"
type="week"
format="yyyy 第 WW 周"
placeholder="选择周"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100px"
:picker-options="{ firstDayOfWeek: 1 }"
/>
<el-date-picker
v-else-if="timeType === 5"
v-model="time"
type="date"
placeholder="选择日期"
style="width: 100px"
/>
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="">
<el-button type="primary" @click="onSubmit">搜索</el-button> <el-button type="primary" @click="onSubmit">搜索</el-button>
<el-button>导出</el-button> <el-button @click="toExport">导出</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<LineChart :option="echartOption"></LineChart> <LineChart ref="lineChartRef" :option="echartOption" />
</div> </div>
</template> </template>
<script> <script>
import LineChart from "./LineChart"; import LineChart from "./LineChart";
import { getCircleStatistics } from "@/api/circle";
import dayjs from "dayjs";
export default { export default {
components: { LineChart }, components: { LineChart },
data() { data() {
return { return {
options: [ formInline: {
{ groupId: this.$route.query.id,
value: "1", startDate: "",
label: "日" endDate: ""
}, },
{ timeType: 1,
value: "2", time: "",
label: "月" currentData: {},
}, dataList: [],
{
value: "3",
label: "年"
}
],
echartOption: { echartOption: {
tooltip: { tooltip: {
trigger: "axis" trigger: "axis"
}, },
legend: { legend: {
data: ["Email", "Union Ads"] data: [
"老师发布内容数",
"助教发布内容数",
"用户互动内容数",
"老师私聊内容数",
"助教私聊内容数",
"用户私聊内容数"
]
}, },
grid: { grid: {
top: "40px", top: "40px",
@ -86,21 +140,169 @@ export default {
xAxis: { xAxis: {
type: "category", type: "category",
boundaryGap: false, boundaryGap: false,
data: ["Mon", "Tue"] data: []
}, },
yAxis: { yAxis: {
type: "value" type: "value"
}, },
series: [ series: []
{
name: "Email",
type: "line",
stack: "Total",
data: [120, 132, 101, 134, 90, 230, 210]
}
]
} }
}; };
},
watch: {
timeType(value) {
this.time = this.formInline.startDate = this.formInline.endDate = "";
},
time(value) {
this.formInline.startDate = value;
if (value === null || value === "") {
return (this.formInline.endDate = null);
}
if (this.formInline.startDate !== null) {
this.formInline.startDate = dayjs(this.formInline.startDate).format(
"YYYY-MM-DD HH:mm:ss"
);
}
if (this.timeType == 2) {
this.formInline.endDate = dayjs(value)
.add(1, "year")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 3) {
this.formInline.endDate = dayjs(value)
.add(1, "month")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 4) {
this.formInline.startDate = dayjs(value)
.subtract(1, "day")
.format("YYYY-MM-DD HH:mm:ss");
this.formInline.endDate = dayjs(value)
.add(6, "day")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 5) {
this.formInline.endDate = dayjs(value)
.add(1, "day")
.format("YYYY-MM-DD HH:mm:ss");
}
}
},
created() {
this.getData();
},
methods: {
async getData() {
const ret = await getCircleStatistics(this.formInline);
if (ret && ret.data) {
this.dataList = ret.data;
if (
!this.formInline.endDate ||
this.formInline.endDate === dayjs().format("YYYY-MM-DD")
) {
this.currentData = ret.data[ret.data.length - 1];
}
const xAxisData = [];
const seriesData = {
advisorGroupContent: {
name: "老师发布内容数",
type: "line",
stack: "Total",
data: []
},
assistantGroupContent: {
name: "助教发布内容数",
type: "line",
stack: "Total",
data: []
},
customerGroupContent: {
name: "用户互动内容数",
type: "line",
stack: "Total",
data: []
},
advisorPrivateContent: {
name: "老师私聊内容数",
type: "line",
stack: "Total",
data: []
},
assistantPrivateContent: {
name: "助教私聊内容数",
type: "line",
stack: "Total",
data: []
},
customerPrivateContent: {
name: "用户私聊内容数",
type: "line",
stack: "Total",
data: []
}
};
this.dataList.forEach(item => {
for (const key in seriesData) {
seriesData[key].data.push(item[key]);
}
xAxisData.push(item.date);
});
this.echartOption.xAxis.data = [];
this.echartOption.xAxis.data = xAxisData;
this.echartOption.series = [];
for (const key in seriesData) {
this.echartOption.series.push(seriesData[key]);
}
this.$nextTick(() => {
this.$refs.lineChartRef.drawChart();
});
}
},
onSubmit() {
this.getData();
},
formatJson(filterVal, jsonData) {
return jsonData.map(v =>
filterVal.map(j => {
return v[j];
})
);
},
async toExport() {
this.downloadLoading = true;
const { data, code, message } = await getCircleStatistics(
this.formInline
);
if (code === 0) {
import("@/utils/export2Excel").then(excel => {
const tHeader = [
"日期",
"老师发布内容数",
"助教发布内容数",
"用户互动内容数",
"老师私聊内容数",
"助教私聊内容数",
"用户私聊内容数"
];
const filterVal = [
"date",
"advisorGroupContent",
"assistantGroupContent",
"customerGroupContent",
"advisorPrivateContent",
"assistantPrivateContent",
"customerPrivateContent"
];
const excelData = this.formatJson(filterVal, data);
excel.export_json_to_excel({
header: tHeader,
data: excelData,
filename: "圈子内容统计"
});
console.log("list=>", data);
});
} else {
this.$message.error(message);
}
this.downloadLoading = false;
}
} }
}; };
</script> </script>

View File

@ -4,80 +4,121 @@
<ul class="data-list"> <ul class="data-list">
<li> <li>
<p>总成员数</p> <p>总成员数</p>
<h5>100</h5> <h5>{{ currentData.totalMembers }}</h5>
</li> </li>
<li> <li>
<p>今日访问成员数</p> <p>今日访问成员数</p>
<h5>500</h5> <h5>{{ currentData.visitedMembers }}</h5>
</li> </li>
<li> <li>
<p>今日新增成员数</p> <p>今日新增成员数</p>
<h5>500</h5> <h5>{{ currentData.newMembers }}</h5>
</li> </li>
<li> <li>
<p>今日动态成员数</p> <p>今日发互动成员数</p>
<h5>500</h5> <h5>{{ currentData.interactionMembers }}</h5>
</li>
<li>
<p>今日发私聊成员数</p>
<h5>{{ currentData.privateChatMembers }}</h5>
</li> </li>
</ul> </ul>
<div class="chart-header"> <div class="chart-header">
<h5>成员趋势</h5> <h5>成员趋势</h5>
<el-form :inline="true" size="mini" :model="formInline" class="my-form"> <el-form :inline="true" size="mini" :model="formInline" class="my-form">
<el-form-item label=""> <el-form-item label="" prop="timeType">
<el-select v-model="value" placeholder="请选择"> <el-select
<el-option v-model="timeType"
v-for="item in options" class="filter-item"
:key="item.value" style="width: 100px"
:label="item.label"
:value="item.value"
> >
</el-option> <el-option
v-for="item in [
{ label: '总数据', id: 1 },
{ label: '年', id: 2 },
{ label: '月', id: 3 },
{ label: '周', id: 4 },
{ label: '日', id: 5 }
]"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="" prop="username">
<el-date-picker <el-date-picker
v-model="value1" v-if="timeType === 2"
type="daterange" v-model="time"
range-separator="至" type="year"
start-placeholder="开始日期" placeholder="选择年"
end-placeholder="结束日期" value-format="yyyy-MM-dd HH:mm:ss"
> style="width: 100px"
</el-date-picker> />
<el-date-picker
v-else-if="timeType === 3"
v-model="time"
type="month"
placeholder="选择月"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100px"
/>
<el-date-picker
v-else-if="timeType === 4"
v-model="time"
type="week"
format="yyyy 第 WW 周"
placeholder="选择周"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100px"
:picker-options="{ firstDayOfWeek: 1 }"
/>
<el-date-picker
v-else-if="timeType === 5"
v-model="time"
type="date"
placeholder="选择日期"
style="width: 100px"
/>
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="">
<el-button type="primary" @click="onSubmit">搜索</el-button> <el-button type="primary" @click="onSubmit">搜索</el-button>
<el-button>导出</el-button> <el-button @click="toExport">导出</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<LineChart :option="echartOption"></LineChart> <LineChart ref="lineChartRef" :option="echartOption" />
</div> </div>
</template> </template>
<script> <script>
import LineChart from "./LineChart"; import LineChart from "./LineChart";
import { getCircleStatistics } from "@/api/circle";
import dayjs from "dayjs";
export default { export default {
components: { LineChart }, components: { LineChart },
data() { data() {
return { return {
options: [ formInline: {
{ groupId: this.$route.query.id,
value: "1", startDate: "",
label: "日" endDate: ""
}, },
{ timeType: 1,
value: "2", time: "",
label: "月" currentData: {},
}, dataList: [],
{
value: "3",
label: "年"
}
],
echartOption: { echartOption: {
tooltip: { tooltip: {
trigger: "axis" trigger: "axis"
}, },
legend: { legend: {
data: ["Email", "Union Ads"] data: [
"总成员数",
"访问成员数",
"新增成员数",
"发互动成员数",
"发私聊成员数"
]
}, },
grid: { grid: {
top: "40px", top: "40px",
@ -94,27 +135,161 @@ export default {
xAxis: { xAxis: {
type: "category", type: "category",
boundaryGap: false, boundaryGap: false,
data: ["Mon", "Tue"] data: []
}, },
yAxis: { yAxis: {
type: "value" type: "value"
}, },
series: [ series: []
{
name: "Email",
type: "line",
stack: "Total",
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: "Union Ads",
type: "line",
stack: "Total",
data: [220, 182, 191, 234, 290, 330, 310]
}
]
} }
}; };
},
watch: {
timeType(value) {
this.time = this.formInline.startDate = this.formInline.endDate = "";
},
time(value) {
this.formInline.startDate = value;
if (value === null || value === "") {
return (this.formInline.endDate = null);
}
if (this.formInline.startDate !== null) {
this.formInline.startDate = dayjs(this.formInline.startDate).format(
"YYYY-MM-DD HH:mm:ss"
);
}
if (this.timeType == 2) {
this.formInline.endDate = dayjs(value)
.add(1, "year")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 3) {
this.formInline.endDate = dayjs(value)
.add(1, "month")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 4) {
this.formInline.startDate = dayjs(value)
.subtract(1, "day")
.format("YYYY-MM-DD HH:mm:ss");
this.formInline.endDate = dayjs(value)
.add(6, "day")
.format("YYYY-MM-DD HH:mm:ss");
} else if (this.timeType == 5) {
this.formInline.endDate = dayjs(value)
.add(1, "day")
.format("YYYY-MM-DD HH:mm:ss");
}
}
},
created() {
this.getData();
},
methods: {
async getData() {
const ret = await getCircleStatistics(this.formInline);
if (ret && ret.data) {
this.dataList = ret.data;
if (
!this.formInline.endDate ||
this.formInline.endDate === dayjs().format("YYYY-MM-DD")
) {
this.currentData = ret.data[ret.data.length - 1];
}
const xAxisData = [];
const seriesData = {
totalMembers: {
name: "总成员数",
type: "line",
stack: "Total",
data: []
},
visitedMembers: {
name: "访问成员数",
type: "line",
stack: "Total",
data: []
},
newMembers: {
name: "新增成员数",
type: "line",
stack: "Total",
data: []
},
interactionMembers: {
name: "发互动成员数",
type: "line",
stack: "Total",
data: []
},
privateChatMembers: {
name: "发私聊成员数",
type: "line",
stack: "Total",
data: []
}
};
this.dataList.forEach(item => {
for (const key in seriesData) {
seriesData[key].data.push(item[key]);
}
xAxisData.push(item.date);
});
this.echartOption.xAxis.data = [];
this.echartOption.xAxis.data = xAxisData;
this.echartOption.series = [];
for (const key in seriesData) {
this.echartOption.series.push(seriesData[key]);
}
this.$nextTick(() => {
this.$refs.lineChartRef.drawChart();
});
}
},
onSubmit() {
this.getData();
},
formatJson(filterVal, jsonData) {
return jsonData.map(v =>
filterVal.map(j => {
return v[j];
})
);
},
async toExport() {
this.downloadLoading = true;
const { data, code, message } = await getCircleStatistics(
this.formInline
);
if (code === 0) {
import("@/utils/export2Excel").then(excel => {
const tHeader = [
"日期",
"总成员数",
"访问成员数",
"新增成员数",
"发互动成员数",
"发私聊成员数"
];
const filterVal = [
"date",
"totalMembers",
"visitedMembers",
"newMembers",
"interactionMembers",
"privateChatMembers"
];
const excelData = this.formatJson(filterVal, data);
excel.export_json_to_excel({
header: tHeader,
data: excelData,
filename: "圈子成员统计"
});
console.log("list=>", data);
});
} else {
this.$message.error(message);
}
this.downloadLoading = false;
}
} }
}; };
</script> </script>
@ -151,6 +326,7 @@ export default {
padding: 0; padding: 0;
font-size: 24px; font-size: 24px;
line-height: 40px; line-height: 40px;
height: 40px;
} }
} }
} }

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="module-content"> <div class="module-content">
<h4>学员列表</h4> <h4>学员列表</h4>
<div class="table-area">
<el-form <el-form
:inline="true" :inline="true"
size="mini" size="mini"
@ -11,18 +12,18 @@
<el-input <el-input
v-model="formInline.user" v-model="formInline.user"
placeholder="请输入uid或者昵称" placeholder="请输入uid或者昵称"
></el-input> />
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="">
<el-select v-model="formInline.region" placeholder="选择在期状态"> <el-select v-model="formInline.region" placeholder="选择在期状态">
<el-option label="区域一" value="shanghai"></el-option> <el-option label="区域一" value="shanghai" />
<el-option label="区域二" value="beijing"></el-option> <el-option label="区域二" value="beijing" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="">
<el-select v-model="formInline.region" placeholder="选择禁言状态"> <el-select v-model="formInline.region" placeholder="选择禁言状态">
<el-option label="区域一" value="shanghai"></el-option> <el-option label="区域一" value="shanghai" />
<el-option label="区域二" value="beijing"></el-option> <el-option label="区域二" value="beijing" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -37,14 +38,14 @@
style="max-width: 50x;max-height: 50px;" style="max-width: 50x;max-height: 50px;"
alt="" alt=""
srcset="" srcset=""
/> >
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="name" label="uid" width="180"> </el-table-column> <el-table-column prop="name" label="uid" width="180" />
<el-table-column prop="address" label="加入时间"> </el-table-column> <el-table-column prop="address" label="加入时间" />
<el-table-column prop="address" label="到期时间"> </el-table-column> <el-table-column prop="address" label="到期时间" />
<el-table-column prop="address" label="状态"> </el-table-column> <el-table-column prop="address" label="状态" />
<el-table-column prop="address" label="禁言状态"> </el-table-column> <el-table-column prop="address" label="禁言状态" />
<el-table-column fixed="right" label="操作" width="100"> <el-table-column fixed="right" label="操作" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="small">私聊</el-button> <el-button type="text" size="small">私聊</el-button>
@ -53,6 +54,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
</div>
</template> </template>
<script> <script>
export default { export default {
@ -61,7 +63,21 @@ export default {
formInline: {}, formInline: {},
tableData: [{}] tableData: [{}]
}; };
},
methods: {
onSubmit() {}
} }
}; };
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.module-content {
margin-bottom: 20px;
h4 {
margin: 0;
font-size: 18px;
}
}
.table-area {
padding: 20px;
}
</style>

View File

@ -11,9 +11,9 @@
<img <img
v-if="item.userType === 1" v-if="item.userType === 1"
:src="item.advisor ? item.advisor.avatar : defaultAvatar.teacher" :src="item.advisor ? item.advisor.avatar : defaultAvatar.teacher"
/> >
<img v-else-if="item.userType === 3" :src="defaultAvatar.assistant" /> <img v-else-if="item.userType === 3" :src="defaultAvatar.assistant">
<img v-else-if="item.userType === 2" :src="defaultAvatar.student" /> <img v-else-if="item.userType === 2" :src="defaultAvatar.student">
<div class="news-info"> <div class="news-info">
<div class="news-info-top"> <div class="news-info-top">
<div class="news-user"> <div class="news-user">
@ -21,11 +21,12 @@
<label <label
v-if="[1, 3].includes(item.userType)" v-if="[1, 3].includes(item.userType)"
:class="[item.userType === 1 ? 'blue' : 'orange']" :class="[item.userType === 1 ? 'blue' : 'orange']"
>{{ item.userType === 1 ? "老师" : "助教" }}</label >{{ item.userType === 1 ? "老师" : "助教" }}</label>
> <div v-if="item.userType !== 2">
<span><i>1</i>/4</span> <span><i v-if="item.readCount">{{ item.readCount }}</i><i v-if="item.totalCount">{{ item.totalCount }}</i></span>
<span>已读</span> <span>已读</span>
</div> </div>
</div>
<div class="news-opt"> <div class="news-opt">
<el-link <el-link
v-if="type !== 5" v-if="type !== 5"
@ -33,20 +34,17 @@
@click="setMessageRecommend(item)" @click="setMessageRecommend(item)"
>{{ >{{
item.isRecommend === 1 ? "取消精选" : "设为精选" item.isRecommend === 1 ? "取消精选" : "设为精选"
}}</el-link }}</el-link>
>
<el-link <el-link
v-if="item.userType === 2 && type !== 5" v-if="item.userType === 2 && type !== 5"
type="success" type="success"
@click="setReplyMsg(item)" @click="setReplyMsg(item)"
>引用</el-link >引用</el-link>
>
<el-link <el-link
v-if="item.userType === 2 && item.status === 1" v-if="item.userType === 2 && item.status === 1"
type="success" type="success"
@click="updateMessageStatus(item)" @click="updateMessageStatus(item)"
>通过审核</el-link >通过审核</el-link>
>
</div> </div>
</div> </div>
<p>{{ item.createTime }}</p> <p>{{ item.createTime }}</p>
@ -54,7 +52,7 @@
</div> </div>
<div class="new-content"> <div class="new-content">
<p v-if="item.contentType === 1">{{ item.content }}</p> <p v-if="item.contentType === 1">{{ item.content }}</p>
<img v-else :src="item.content" alt="" /> <img v-else :src="item.content" alt="">
</div> </div>
</li> </li>
</ul> </ul>
@ -177,7 +175,7 @@ export default {
this.loading = false; this.loading = false;
}); });
if (ret && ret.code === 0) { if (ret && ret.code === 0) {
let retList = ret.data.list; const retList = ret.data.list;
this.list = this.list.concat(ret.data.list); this.list = this.list.concat(ret.data.list);
retList.forEach(msg => { retList.forEach(msg => {
this.msgIdsObj[msg.id] = msg; this.msgIdsObj[msg.id] = msg;

View File

@ -13,7 +13,7 @@
> >
<li v-for="(item, index) in list" :key="index"> <li v-for="(item, index) in list" :key="index">
<div class="new-header"> <div class="new-header">
<img :src="defaultAvatar.student" /> <img :src="defaultAvatar.student">
<div class="news-info"> <div class="news-info">
<div class="news-info-top"> <div class="news-info-top">
<div class="news-user"> <div class="news-user">
@ -21,30 +21,32 @@
<label <label
v-if="[1, 3].includes(item.userType)" v-if="[1, 3].includes(item.userType)"
:class="[item.userType === 1 ? 'blue' : 'orange']" :class="[item.userType === 1 ? 'blue' : 'orange']"
>{{ item.userType === 1 ? "老师" : "助教" }}</label >{{ item.userType === 1 ? "老师" : "助教" }}</label>
> <div v-if="item.userType !== 2">
<span><i>1</i>/4</span> <span><i v-if="item.readCount">{{ item.readCount }}</i><i v-if="item.totalCount">{{ item.totalCount }}</i></span>
<span>已读</span> <span>已读</span>
</div> </div>
</div> </div>
</div>
<p>{{ item.createTime }}</p> <p>{{ item.createTime }}</p>
</div> </div>
<div class="news-opt"> <div class="news-opt">
<div class="flex"> <div class="flex">
<el-link type="success" @click="setReplyMsg(item)" <el-link
>引用</el-link type="success"
> @click="setReplyMsg(item)"
>引用</el-link>
<el-link <el-link
v-if="item.userType === 2 && item.status === 1" v-if="item.userType === 2 && item.status === 1"
type="success" type="success"
@click="updateMessageStatus(item)" @click="updateMessageStatus(item)"
>通过审核</el-link >通过审核</el-link>
>
</div> </div>
<div class="flex"> <div class="flex">
<el-link type="success" @click="toPrivateChat(item)" <el-link
>私聊</el-link type="success"
> @click="toPrivateChat(item)"
>私聊</el-link>
<el-link type="success" @click="prohibition(item)">{{ <el-link type="success" @click="prohibition(item)">{{
item.isForbidden === 1 ? "取消禁言" : "禁言" item.isForbidden === 1 ? "取消禁言" : "禁言"
@ -54,7 +56,7 @@
</div> </div>
<div class="new-content"> <div class="new-content">
<p v-if="item.contentType === 1">{{ item.content }}</p> <p v-if="item.contentType === 1">{{ item.content }}</p>
<img v-else :src="item.content" alt="" /> <img v-else :src="item.content" alt="">
</div> </div>
</li> </li>
</ul> </ul>
@ -156,7 +158,7 @@ export default {
this.loading = false; this.loading = false;
}); });
if (ret && ret.code === 0) { if (ret && ret.code === 0) {
let retList = ret.data.list; const retList = ret.data.list;
this.list = this.list.concat(retList); this.list = this.list.concat(retList);
retList.forEach(msg => { retList.forEach(msg => {
this.msgIdsObj[msg.id] = msg; this.msgIdsObj[msg.id] = msg;

View File

@ -6,6 +6,9 @@ export default {
stompClient: null stompClient: null
}; };
}, },
beforeDestroy() {
this.stompClient && this.stompClient.deactivate();
},
methods: { methods: {
async getConnectConfig(id) { async getConnectConfig(id) {
const res = await rLiveWsConfig({ id, type: 9 }); const res = await rLiveWsConfig({ id, type: 9 });

View File

@ -91,8 +91,13 @@
v-if="tougu || yunyin || zhujiao || yinxiao" v-if="tougu || yunyin || zhujiao || yinxiao"
type="text" type="text"
size="mini" size="mini"
@click="addTag(scope.row, 1)" @click="addTag(scope.row, 0)"
>查看</el-button> >查看</el-button>
<el-button
type="text"
size="mini"
@click="$router.push(`/circle/data?id=${scope.row.id}`)"
>数据</el-button>
<el-button <el-button
v-if=" v-if="
(tougu || zhujiao || yunyin) && (tougu || zhujiao || yunyin) &&