加活动分组

This commit is contained in:
dengbw
2022-12-07 14:22:47 +08:00
parent e943490ac3
commit 3e24aea2af
19 changed files with 3003 additions and 96 deletions
+83
View File
@@ -0,0 +1,83 @@
import request from '@/utils/request';
/**
* 根据活动id查询分组数据统计
* @param id 活动id
*/
export async function getGroupsStatistics(activityId) {
const res = await request.get('/sylive/groupsStatistics/' + activityId);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取漏头数据
* @param params 查询条件
*/
export async function getGroupsStatisticsFunnel(params) {
const res = await request.get('/sylive/groupsStatistics/funnel', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取分级数据
* @param params 查询条件
*/
export async function getGroupsStatisticsLevel(params) {
const res = await request.get('/sylive/groupsStatistics/level', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取门店数据
* @param params 查询条件
*/
export async function getGroupsStatisticsBiz(params) {
const res = await request.get('/sylive/groupsStatistics/biz', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取顾问数据
* @param params 查询条件
*/
export async function getGroupsStatisticsConsultant(params) {
const res = await request.get('/sylive/groupsStatistics/consultant', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 获取观看下单统计折线图数据
* @param params 查询条件
*/
export async function getGroupsStatisticsWatchOrder(params) {
const res = await request.get('/sylive/groupsStatistics/stacked_watchOrder', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
+154
View File
@@ -0,0 +1,154 @@
import request from '@/utils/request';
/**
* 分页查询用户
* @param params 查询条件
*/
export async function pageGroupsUser(params) {
const res = await request.get('/sylive/groupsUser/page', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询用户列表
* @param params 查询条件
*/
export async function listGroupsUser(params) {
const res = await request.get('/sylive/groupsUser', {
params
});
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询用户
* @param id 用户id
*/
export async function getGroupsUser(id) {
const res = await request.get('/sylive/groupsUser/' + id);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加用户
* @param data 用户信息
*/
export async function addGroupsUser(data) {
const res = await request.post('/sylive/groupsUser', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户
* @param data 用户信息
*/
export async function updateGroupsUser(data) {
const res = await request.put('/sylive/groupsUser', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除用户
* @param id 用户id
*/
export async function removeGroupsUser(id) {
const res = await request.delete('/sylive/groupsUser/' + id);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除用户
* @param data 用户id集合
*/
export async function removeGroupsUsers(data) {
const res = await request.delete('/sylive/groupsUser/batch', {
data
});
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改用户状态
* @param groupsUserId 用户id
* @param status 状态
*/
export async function updateGroupsUserStatus(groupsUserId, status) {
const res = await request.put('/sylive/groupsUser/status', {
groupsUserId,
status
});
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 重置用户密码
* @param userId 用户id
* @param password 密码
* @returns {Promise<string>}
*/
export async function updateGroupsUserPassword(userId, password = '123456') {
const res = await request.put('/sylive/groupsUser/password', {
userId,
password
});
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 导入用户
* @param file excel文件
*/
export async function importGroupsUser(file) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post('/sylive/groupsUser/import', formData);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 检查用户是否存在
* @param field 检查的字段
* @param value 字段的值
* @param id 修改时的id
*/
export async function checkExistence(field, value, id) {
const res = await request.get('/sylive/groupsUser/existence', {
params: { field, value, id }
});
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
+79
View File
@@ -0,0 +1,79 @@
import request from '@/utils/request';
/**
* 分页查询分组
* @param params 查询条件
*/
export async function pageGroups(params) {
const res = await request.get('/sylive/groups/page', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询分组列表
* @param params 查询条件
*/
export async function listGroups(params) {
const res = await request.get('/sylive/groups', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询上级分组列表
* @param params 查询条件
*/
export async function listGroupsParent(params) {
const res = await request.get('/sylive/groups/parent', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加分组
* @param data 分组信息
*/
export async function addGroups(data) {
const res = await request.post('/sylive/groups', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改分组
* @param data 分组信息
*/
export async function updateGroups(data) {
const res = await request.put('/sylive/groups', data);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除分组
* @param id 分组id
*/
export async function removeGroups(id) {
const res = await request.delete('/sylive/groups/' + id);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
+1 -3
View File
@@ -19,9 +19,7 @@ export async function pageTeamUser(params) {
* @param params 查询条件
*/
export async function listTeamUser(params) {
const res = await request.get('/sylive/teamUser', {
params
});
const res = await request.post('/sylive/teamUser/list', params);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
+1 -3
View File
@@ -19,9 +19,7 @@ export async function pageUsers(params) {
* @param params 查询条件
*/
export async function listUsers(params) {
const res = await request.get('/sylive/user', {
params
});
const res = await request.post('/sylive/user/list', params);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
@@ -1,7 +1,7 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
width="750px"
width="800px"
:visible="visible"
:append-to-body="true"
:close-on-click-modal="true"
@@ -18,34 +18,6 @@
placeholder="请输入活动标题"
/>
</el-form-item>
<el-form-item label="所属门店:">
<ele-tree-select
:data="organizationList"
label-key="organizationName"
value-key="organizationId"
v-model="form.bizIds"
:multiple="true"
:clearable="true"
placeholder="请选择所属门店"
:disabled="false"
:default-expand-all="true"
:check-strictly="false"
/>
</el-form-item>
<el-form-item label="所属团队:">
<ele-tree-select
:data="teamList"
label-key="teamName"
value-key="teamId"
v-model="form.teamIds"
:multiple="true"
:clearable="true"
placeholder="请选择所属团队"
:disabled="false"
:default-expand-all="true"
:check-strictly="false"
/>
</el-form-item>
<el-form-item label="背景图片:" prop="bgImg">
<ele-image-upload
v-model="form.bgImg"
@@ -59,6 +31,30 @@
建议尺寸1125X2000主题内容建议做在顶部0~1700像素区域
</div>
</el-form-item>
<el-form-item label="所属机构:" prop="organizationId">
<el-select
v-model="form.organizationId"
placeholder="请选择所属机构"
class="ele-fluid"
>
<el-option
v-for="item in organizationList"
:key="item.organizationId"
:label="item.organizationName"
:value="item.organizationId"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间:" prop="activityStart">
<el-date-picker
type="datetime"
v-model="form.activityStart"
placeholder="请选择活动开始时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
class="ele-fluid"
/>
</el-form-item>
<el-form-item label="直播频道:" prop="channelId">
<el-input
clearable
@@ -90,7 +86,7 @@
<el-form-item label="收款账户:" prop="mchId">
<activity-mch-id-select v-model="form.mchId" />
</el-form-item>
<el-form-item label="支付方式:">
<el-form-item label="支付方式:" v-show="false">
<el-select
clearable
v-model="form.pay.way"
@@ -101,7 +97,7 @@
<el-option label="经销商支付" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="支付价格:" v-show="form.pay.way == 1 ? false : true">
<el-form-item label="支付价格:" v-show="false">
<el-input
clearable
:maxlength="30"
@@ -109,7 +105,7 @@
placeholder="请输入支付价格"
/>
</el-form-item>
<el-form-item label="支付图片:" v-show="form.pay.way == 1 ? false : true">
<el-form-item label="支付图片:" v-show="false">
<ele-image-upload
v-model="form.pay.img"
:limit="1"
@@ -131,14 +127,6 @@
/>
<div class="ele-text-secondary">建议尺寸690X330</div>
</el-form-item>
<el-form-item label="活动简介:" prop="introduction" v-show="false">
<el-input
v-model="form.introduction"
placeholder="请输入活动简介"
:rows="3"
type="textarea"
/>
</el-form-item>
<el-form-item label="banner:">
<ele-image-upload
v-model="form.banner"
@@ -197,6 +185,85 @@
新增分享文案
</el-button>
</el-form-item>
<el-form-item label="活动底部:">
<el-table :data="form.bottoms" :border="true" style="width: 100%">
<el-table-column label="标题">
<template v-slot="{ row }">
<el-input v-model="row.title" placeholder="标题" />
</template>
</el-table-column>
<el-table-column label="类型" align="center">
<template v-slot="{ row }">
<el-select v-model="row.urlType" class="ele-fluid">
<el-option label="链接" value="link" />
<el-option label="小程序" value="miniProgram" />
</el-select>
</template>
</el-table-column>
<el-table-column label="链接" width="180px" align="center">
<template v-slot="{ row }">
<el-input
v-model="row.url"
placeholder="请输入链接"
:rows="2"
type="textarea"
/>
<el-input
v-if="row.urlType == 'miniProgram'"
v-model="row.miniProgramId"
placeholder="小程序原始id"
style="padding-top: 5px"
:rows="2"
type="textarea"
/>
</template>
</el-table-column>
<el-table-column label="图标" width="120px" align="center">
<template v-slot="{ row, $index }">
<ele-image-upload
v-model="row.icon"
:limit="1"
:drag="true"
:multiple="false"
:upload-handler="$index == 0 ? uploadHandler0 : uploadHandler1"
@upload="onUpload"
/>
</template>
</el-table-column>
<el-table-column
label="操作"
width="70px"
align="center"
:resizable="false"
>
<template v-slot="{ row, $index }">
<span class="ele-action">
<el-popconfirm
title="确定要删除此底部吗?"
@confirm="removeBottoms(row, $index)"
>
<template v-slot:reference>
<el-link
icon="el-icon-delete"
type="danger"
:underline="false"
>
删除
</el-link>
</template>
</el-popconfirm>
</span>
</template>
</el-table-column>
</el-table>
<el-button
icon="el-icon-plus"
style="width: 100%; margin-top: 15px"
@click="addBottoms"
>
新增活动底部菜单
</el-button>
</el-form-item>
<el-form-item label="客服链接:">
<el-input
clearable
@@ -240,37 +307,39 @@
import request from '@/utils/request';
import { addActivity, updateActivity } from '@/api/sylive/activity';
export default {
components: { TinymceEditor, ActivityMchIdSelect, EleImageUpload },
components: {
TinymceEditor,
ActivityMchIdSelect,
EleImageUpload
},
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 全部机构
organizationList: Array,
// 全部团队
teamList: Array
// 修改回显的数据
data: Object
},
data() {
const defaultForm = {
activityId: null,
bizIds: [],
teamIds: [],
title: '',
channelId: '',
dateRange: '',
introduction: '',
shareTitle: [''],
shareImg: [],
bgImg: [],
channelImg: [],
banner: [],
sharePhoto: [],
organizationId: null,
drawCode: '',
activityStart: '',
mchId: '1604032585',
protocolTitle: '',
protocol: '',
serviceLink: '',
bottoms: [],
pay: { way: 1, price: '', img: [] }
};
return {
@@ -294,6 +363,20 @@
trigger: 'blur'
}
],
organizationId: [
{
required: true,
message: '请选择所属机构',
trigger: 'blur'
}
],
activityStart: [
{
required: true,
message: '请选择活动开始时间',
trigger: 'blur'
}
],
bgImg: [
{
required: true,
@@ -472,7 +555,6 @@
this.form.sharePhoto.push(item);
this.onUpload(item);
},
/* 上传事件 */
shareImgHandler(file) {
const item = {
file,
@@ -494,6 +576,66 @@
this.form.shareImg.push(item);
this.onUpload(item);
},
/* 添加 */
addBottoms() {
if (this.form.bottoms.length >= 2) {
this.$message.error('最多添加2个活动底部菜单');
return;
}
this.form.bottoms.push({
urlType: 'link',
title: '',
url: '',
miniProgramId: '',
icon: []
});
},
/* 删除 */
removeBottoms(_row, index) {
this.form.bottoms.splice(index, 1);
},
uploadHandler0(file) {
const item = {
file,
uid: file.uid,
name: file.name,
progress: 0,
status: null
};
if (!file.type.startsWith('image')) {
this.$message.error('只能选择图片');
return;
}
if (file.size / 1024 / 1024 > 2) {
this.$message.error('大小不能超过 2MB');
return;
}
item.url = window.URL.createObjectURL(file);
// 关键就是这里要自己 push 添加数据而不是靠 v-modal 自动更新
this.form.bottoms[0]['icon'].push(item);
this.onUpload(item);
},
uploadHandler1(file) {
const item = {
file,
uid: file.uid,
name: file.name,
progress: 0,
status: null
};
if (!file.type.startsWith('image')) {
this.$message.error('只能选择图片');
return;
}
if (file.size / 1024 / 1024 > 2) {
this.$message.error('大小不能超过 2MB');
return;
}
item.url = window.URL.createObjectURL(file);
// 关键就是这里要自己 push 添加数据而不是靠 v-modal 自动更新
this.form.bottoms[1]['icon'].push(item);
this.onUpload(item);
},
/* 上传 item */
onUpload(item) {
item.status = 'uploading';
@@ -535,7 +677,7 @@
this.form.shareTitle.push('');
}
} else {
//this.form.organizationId = this.organizationId;
this.form.bottoms = [];
this.form.bgImg = [];
this.form.shareImg = [];
this.isUpdate = false;
@@ -550,7 +692,7 @@
</script>
<style>
.ele-image-upload-list .ele-image-upload-item {
margin-top: 5px;
margin-bottom: 5px;
margin-top: 2px;
margin-bottom: 2px;
}
</style>
+14 -38
View File
@@ -53,7 +53,10 @@
<i class="el-icon-arrow-down"></i>
</el-link>
<template v-slot:dropdown>
<el-dropdown-menu>
<el-dropdown-menu v-if="row.groups == 1">
<el-dropdown-item command="gro">分组数据统计</el-dropdown-item>
</el-dropdown-menu>
<el-dropdown-menu v-else>
<el-dropdown-item command="org">机构数据统计</el-dropdown-item>
<el-dropdown-item command="team">团队数据统计</el-dropdown-item>
</el-dropdown-menu>
@@ -84,6 +87,7 @@
<el-dropdown-menu>
<el-dropdown-item command="activity">修改活动</el-dropdown-item>
<el-dropdown-item command="goods">修改商品</el-dropdown-item>
<el-dropdown-item command="groups">修改分组</el-dropdown-item>
<!--
<el-dropdown-item command="item">修改权益商品</el-dropdown-item>
<el-dropdown-item command="coupon">修改券</el-dropdown-item>
@@ -110,7 +114,6 @@
:data="current"
:visible.sync="showEdit"
:organization-list="organizationList"
:team-list="teamList"
@done="reload"
/>
<!-- 编辑商品弹窗 -->
@@ -142,8 +145,7 @@
removeActivitys,
updateActivityStatus
} from '@/api/sylive/activity';
import { listOrganizations } from '@/api/sylive/organization';
import { listTeams } from '@/api/sylive/team';
import { listOrganizationParent } from '@/api/sylive/organization';
export default {
name: 'syliveActivity',
@@ -156,12 +158,8 @@
},
data() {
return {
// 加载状态
loading: true,
// 门店数据
organizationList: [],
// 团队数据
teamList: [],
// 表格列配置
columns: [
{
@@ -261,50 +259,29 @@
this.showEditCoupon = true;
} else if (command === 'goods') {
this.$router.replace('/sylive/goods?id=' + row.activityId);
} else if (command === 'groups') {
this.$router.replace('/sylive/groups?id=' + row.activityId);
}
},
/* 打开数据统计页 */
dropStatistics(command, row) {
var url = '';
if (command === 'org') {
if (command === 'gro') {
url = '/sylive/groups-statistics?id=' + row.activityId;
} else if (command === 'org') {
url = '/sylive/activity/statistics?id=' + row.activityId;
this.$router.replace(url);
} else if (command === 'team') {
url = '/sylive/activity/statistics-team?id=' + row.activityId;
this.$router.replace(url);
}
this.$router.replace(url);
},
/* 查询机构 */
organizationQuery() {
this.loading = true;
listOrganizations({ unOrganizationType: 4 })
listOrganizationParent({ parentId: 0 })
.then((list) => {
this.loading = false;
this.organizationList = this.$util.toTreeData({
data: list,
idField: 'organizationId',
parentIdField: 'parentId'
});
this.organizationList = list;
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
},
/* 查询团队 */
teamQuery() {
this.loading = true;
listTeams({ unTeamType: 3 })
.then((list) => {
this.loading = false;
this.teamList = this.$util.toTreeData({
data: list,
idField: 'teamId',
parentIdField: 'parentId'
});
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
},
@@ -312,7 +289,6 @@
datasource({ page, limit, where, order }) {
if (page == 1) {
this.organizationQuery();
this.teamQuery();
}
return pageActivity({ ...where, ...order, page, limit });
},
@@ -0,0 +1,179 @@
<template>
<el-card shadow="never">
<!-- 数据表格 -->
<ele-pro-table
ref="table"
title="门店统计"
:columns="columns"
:datasource="datasource"
size="mini"
>
<template v-slot:toolkit>
<div class="list-tool-item">
<el-select
v-model="day"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.days"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
<div class="list-tool-item">
<el-select
v-model="itemId"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.goods"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
</template>
</ele-pro-table>
</el-card>
</template>
<script>
import { getGroupsStatisticsBiz } from '@/api/sylive/groups-statistics';
export default {
props: { data: Object },
data() {
return {
activityId: null,
// 表格选中数据
day: '',
itemId: '',
selection: [],
// 表格列配置
columns: [
{
prop: 'bizName',
label: '门店名称',
align: 'center',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'consultant',
label: '参与顾问数',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'allConsultant',
label: '全部顾问数',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'browse',
label: '浏览数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'subscribe',
label: '预约数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'watch',
label: '观看数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'order',
label: '订单数(单)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'livePV',
label: '观看数(人次)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'watchDuration',
label: '人均观看(分)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
}
]
};
},
methods: {
/* 表格数据源 */
datasource({ page, limit, order }) {
return getGroupsStatisticsBiz({
...order,
page,
limit,
activityId: this.activityId,
day: this.day,
itemId: this.itemId
});
},
/* 更新选中数据 */
updateValue() {
this.$refs.table.reload({ page: 1 });
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== '/sylive/groups-statistics') {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = activityId;
if (this.$refs.table) {
this.$refs.table.reload({ page: 1 });
}
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
.list-tool-divider {
padding: 0 12px;
}
</style>
@@ -0,0 +1,163 @@
<template>
<el-card shadow="never">
<!-- 数据表格 -->
<ele-pro-table
ref="table"
title="顾问统计"
:columns="columns"
:datasource="datasource"
size="mini"
>
<template v-slot:toolkit>
<div class="list-tool-item">
<el-select
v-model="day"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.days"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
<div class="list-tool-item">
<el-select
v-model="itemId"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.goods"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
</template>
</ele-pro-table>
</el-card>
</template>
<script>
import { getGroupsStatisticsConsultant } from '@/api/sylive/groups-statistics';
export default {
props: { data: Object },
data() {
return {
activityId: null,
day: '',
itemId: '',
// 表格列配置
columns: [
{
prop: 'consultantName',
label: '顾问名称',
align: 'center',
showOverflowTooltip: true,
minWidth: 150
},
{
prop: 'browse',
label: '浏览数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'subscribe',
label: '预约数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'watch',
label: '观看数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'order',
label: '订单数(单)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'livePV',
label: '观看数(人次)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'watchDuration',
label: '人均观看(分)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
}
]
};
},
methods: {
/* 表格数据源 */
datasource({ page, limit, order }) {
return getGroupsStatisticsConsultant({
...order,
page,
limit,
activityId: this.activityId,
day: this.day,
itemId: this.itemId
});
},
/* 更新选中数据 */
updateValue() {
this.$refs.table.reload({ page: 1 });
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== '/sylive/groups-statistics') {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = activityId;
if (this.$refs.table) {
this.$refs.table.reload({ page: 1 });
}
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
.list-tool-divider {
padding: 0 12px;
}
</style>
@@ -0,0 +1,317 @@
<template>
<el-card shadow="never">
<!-- 数据表格 -->
<ele-pro-table
ref="table"
:title="title"
:columns="columns"
:datasource="datasource"
:need-page="false"
size="mini"
>
<template v-slot:toolkit>
<div class="list-tool-item">
<el-select
v-model="day"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.days"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
<div class="list-tool-item">
<el-select
v-model="itemId"
clearable
class="ele-fluid"
@input="updateValue"
>
<el-option
v-for="item in data.goods"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
<div class="list-tool-divider">
<el-divider direction="vertical" />
</div>
</template>
</ele-pro-table>
<div
style="position: relative; margin-top: 40px"
v-if="areaChartOption.legend.data.length > 0"
>
<div
style="
position: absolute;
top: 0;
right: 40px;
width: 200px;
z-index: 1;
"
>
<el-select
class="ele-fluid"
v-model="tempkpi"
placeholder="请选择"
@change="changeKpi($event)"
>
<el-option
v-for="(option, index) in kpioption"
:key="index"
:value="option.kpi"
:label="option.title"
/>
</el-select>
</div>
<v-chart
ref="areaChart"
style="width: 100%; height: 500px"
:option="areaChartOption"
/>
</div>
</el-card>
</template>
<script>
import { getGroupsStatisticsLevel } from '@/api/sylive/groups-statistics';
import { getStatisticsStackedArea } from '@/api/sylive/activity';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import {
LegendComponent,
GridComponent,
ToolboxComponent,
TooltipComponent
} from 'echarts/components';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { echartsMixin } from '@/utils/echarts-mixin';
use([
CanvasRenderer,
GridComponent,
TooltipComponent,
LegendComponent,
ToolboxComponent,
LineChart
]);
export default {
components: { VChart },
mixins: [echartsMixin(['areaChart'])],
props: { data: Object, level: Number, title: String },
data() {
return {
activityId: null,
// 表格选中数据
day: '',
itemId: '',
selection: [],
// 表格列配置
columns: [
{
prop: 'groupsName',
label: '分级名称',
align: 'center',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'biz',
label: '参与门店数',
align: 'center',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'consultant',
label: '参与顾问数',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'allConsultant',
label: '全部顾问数',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'browse',
label: '浏览数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'subscribe',
label: '预约数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'watch',
label: '观看数(人)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'order',
label: '订单数(单)',
align: 'center',
sortable: 'custom',
showOverflowTooltip: true,
minWidth: 120
},
{
prop: 'livePV',
label: '观看数(人次)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
},
{
prop: 'watchDuration',
label: '人均观看(分)',
align: 'center',
showOverflowTooltip: true,
minWidth: 100
}
],
kpi: 'browse',
tempkpi: 'browse',
kpioption: [
{
kpi: 'browse',
title: '浏览数'
},
{
kpi: 'subscribe',
title: '预约数'
}
],
areaChartOption: {
legend: {
data: []
}
} //折线图
};
},
methods: {
/* 表格数据源 */
datasource({ page, limit, order }) {
return getGroupsStatisticsLevel({
...order,
page,
limit,
activityId: this.activityId,
day: this.day,
itemId: this.itemId,
level: this.level
});
},
//切换区域统计折线图
changeKpi(value) {
if (value != this.kpi) {
this.kpi = value;
this.getStatisticsStackedArea();
}
},
//区域统计折线图
getStatisticsStackedArea() {
if (this.activityId) {
//暂时屏蔽
return;
}
this.areaChartOption = { legend: { data: [] } };
getStatisticsStackedArea({
activityId: this.activityId,
kpi: this.kpi
}).then((res) => {
this.areaChartOption = {
tooltip: {
trigger: 'axis'
},
legend: {
x: 'left',
padding: [13, 260, 0, 40],
data: res.legendData
},
grid: {
left: '3%',
right: '4%',
top: res.legendData.length < 15 ? '14%' : '17%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: res.xAxisData
},
yAxis: {
type: 'value'
},
series: res.yAxisSeries
};
});
},
/* 更新选中数据 */
updateValue() {
this.$refs.table.reload({ page: 1 });
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== '/sylive/groups-statistics') {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = activityId;
this.getStatisticsStackedArea();
if (this.$refs.table) {
this.$refs.table.reload({ page: 1 });
}
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
.list-tool-divider {
padding: 0 12px;
}
</style>
@@ -0,0 +1,107 @@
<template>
<el-card shadow="never" v-if="watchOrderOption.legend.data.length > 0">
<div>
<v-chart
ref="watchOrder"
style="width: 100%; height: 500px"
:option="watchOrderOption"
/>
</div>
</el-card>
</template>
<script>
import { getGroupsStatisticsWatchOrder } from '@/api/sylive/groups-statistics';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import {
LegendComponent,
GridComponent,
ToolboxComponent,
TooltipComponent
} from 'echarts/components';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { echartsMixin } from '@/utils/echarts-mixin';
use([
CanvasRenderer,
GridComponent,
TooltipComponent,
LegendComponent,
ToolboxComponent,
LineChart
]);
export default {
components: { VChart },
mixins: [echartsMixin(['watchOrder'])],
data() {
return {
activityId: null,
watchOrderOption: {
legend: {
data: []
}
} //折线图
};
},
methods: {
//区域统计折线图
query() {
this.watchOrderOption = { legend: { data: [] } };
getGroupsStatisticsWatchOrder({
activityId: this.activityId
}).then((res) => {
this.watchOrderOption = {
tooltip: {
trigger: 'axis'
},
legend: {
padding: [13, 260, 0, 40],
data: res.legendData
},
grid: {
left: '3%',
right: '4%',
top: '10%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: res.xAxisData
},
yAxis: {
type: 'value'
},
series: res.yAxisSeries
};
});
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== '/sylive/groups-statistics') {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = activityId;
this.query();
},
immediate: true
}
}
};
</script>
@@ -0,0 +1,404 @@
<template>
<div class="ele-body ele-body-card" v-loading="loading">
<el-row :gutter="15">
<el-col
v-for="(item, index) in statistics.activityData1.list"
:key="index"
:style="statistics.activityData1.style"
>
<el-card class="analysis-chart-card" shadow="never">
<template v-slot:header>
<div class="ele-cell">
<div class="ele-cell-content">{{ item.name }}</div>
</div>
</template>
<div class="analysis-chart-card-num ele-text-heading">
{{ item.value }}
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="15">
<el-col
v-for="(item, index) in statistics.activityData2.list"
:key="index"
:style="statistics.activityData2.style"
>
<el-card :class="item.url != '' ? 'el-header_add' : ''" shadow="never">
<template v-slot:header>
<div class="ele-cell" @click="openPiechart(item.url, item.name)">
<div class="ele-cell-content">{{ item.name }}</div>
<i class="el-icon-arrow-right" v-if="item.url != ''"></i>
</div>
</template>
<div
class="analysis-chart-card-num ele-text-heading"
@click="openPiechart(item.url, item.name)"
>
{{ item.value }}
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="15">
<el-col
v-bind="styleResponsive ? { lg: 3, md: 8 } : { span: 3 }"
style="width: 50%"
>
<el-card shadow="never" body-style="padding: 0px">
<div class="ele-table-tool ranking-order-header">
<div class="ele-table-tool-title">
<div class="ele-table-tool-title-label">
<h6 style="font-size: 15px">
{{ preheatingChartOption.title }}
</h6>
</div>
</div>
<div class="list-tool-item">
<el-select
v-model="itemIdPreheating"
clearable
class="ele-fluid"
@input="queryFunnel('left')"
>
<el-option
v-for="item in statistics.tableData.goods"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
</div>
<div
style="width: 500px; margin: 0 auto; padding-top: 7px"
v-if="preheatingChartOption.title"
>
<v-chart
ref="funnelChart"
style="width: 500px; height: 303px"
:option="preheatingChartOption.chartOption"
/>
</div>
<ele-empty v-else v-show="true"></ele-empty>
</el-card>
</el-col>
<el-col
v-bind="styleResponsive ? { lg: 3, md: 8 } : { span: 3 }"
style="width: 50%"
>
<el-card shadow="never" body-style="padding: 0px">
<div class="ele-table-tool ranking-order-header">
<div class="ele-table-tool-title">
<div class="ele-table-tool-title-label">
<h6 style="font-size: 15px">{{ liveChartOption.title }}</h6>
</div>
</div>
<div class="list-tool-item">
<el-select
v-model="itemIdLive"
clearable
class="ele-fluid"
@input="queryFunnel('right')"
>
<el-option
v-for="item in statistics.tableData.goods"
:key="item.value"
:value="item.value"
:label="item.name"
/>
</el-select>
</div>
</div>
<div
style="width: 500px; margin: 0 auto; padding-top: 7px"
v-if="liveChartOption.title"
>
<v-chart
ref="funnelChart"
style="width: 500px; height: 303px"
:option="liveChartOption.chartOption"
/>
</div>
<ele-empty v-else v-show="true"></ele-empty>
</el-card>
</el-col>
</el-row>
<watchOrderLinechart />
<level-table
v-for="(item, index) in statistics.levelData"
:key="index"
:data="statistics.tableData"
:level="item.value"
:title="item.title"
/>
<biz-table :data="statistics.tableData" />
<consultant-table :data="statistics.tableData" />
</div>
</template>
<script>
import { setPageTabTitle } from '@/utils/page-tab-util';
import {
getGroupsStatistics,
getGroupsStatisticsFunnel
} from '@/api/sylive/groups-statistics';
const ROUTE_PATH = '/sylive/groups-statistics';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { FunnelChart } from 'echarts/charts';
import { TooltipComponent } from 'echarts/components';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { echartsMixin } from '@/utils/echarts-mixin';
import LevelTable from './components/level-table.vue';
import BizTable from './components/biz-table.vue';
import ConsultantTable from './components/consultant-table.vue';
import watchOrderLinechart from './components/watchOrder-linechart.vue';
use([CanvasRenderer, TooltipComponent, FunnelChart]);
export default {
name: 'SyliveGroupsStatistics',
components: {
VChart,
LevelTable,
BizTable,
ConsultantTable,
watchOrderLinechart
},
mixins: [echartsMixin(['funnelChart'])],
data() {
return {
activityId: null,
itemIdRanking: '',
itemIdPreheating: '',
itemIdLive: '',
statistics: {
activityData1: { list: [], style: '' },
activityData2: { list: [], style: '' },
levelData: [],
tableData: { days: [], goods: [] }
},
loading: true,
browseRank: { area: [], biz: [], consultant: [] },
subscribeRank: { area: [], biz: [], consultant: [] },
watchRank: { area: [], biz: [], consultant: [] },
orderRank: { area: [], biz: [], consultant: [] },
// 词云图表配置
preheatingChartOption: { title: '', chartOption: {} },
liveChartOption: { title: '', chartOption: {} }
};
},
computed: {
// 是否开启响应式布局
styleResponsive() {
return this.$store.state.theme.styleResponsive;
}
},
methods: {
/* 打开饼状图 */
openPiechart(url, title) {
if (url != '') {
this.$router.replace(url + '&title=' + title);
}
},
query() {
this.loading = true;
getGroupsStatistics(Number(this.activityId))
.then((data) => {
this.loading = false;
this.$util.assignObject(this.statistics, {
...data
});
// 修改页签标题
if (this.$route.path === ROUTE_PATH) {
setPageTabTitle(data.title + '的分组数据统计');
}
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
},
queryFunnel(show) {
let itemId = '';
if (show == 'left') {
itemId = this.itemIdPreheating;
} else if (show == 'right') {
itemId = this.itemIdLive;
}
getGroupsStatisticsFunnel({
activityId: this.activityId,
show: show,
itemId: itemId
}).then((data) => {
//浏览转化漏斗((预热阶段)
if (data.funnelPreheating.title) {
this.preheatingChartOption.title = data.funnelPreheating.title;
this.preheatingChartOption.chartOption = {
series: [
{
name: 'Expected',
type: 'funnel',
top: '1%',
bottom: '5%',
left: '2%',
width: '72%',
label: {
position: 'right',
formatter: '{b}'
},
labelLine: {
show: false
},
itemStyle: {
opacity: 0.7
},
data: data.funnelPreheating.expectedData
},
{
name: 'Actual',
type: 'funnel',
top: '1%',
bottom: '5%',
left: '7%',
width: '62%',
maxSize: '62%',
label: {
position: 'inside',
formatter: '{c}',
color: '#000'
},
itemStyle: {
opacity: 0.4,
borderColor: '#fff',
borderWidth: 1
},
data: data.funnelPreheating.actualData,
z: 100
}
]
};
}
//浏览转化漏斗(直播阶段)
if (data.funnelLive.title) {
this.liveChartOption.title = data.funnelLive.title;
this.liveChartOption.chartOption = {
series: [
{
name: 'Expected',
type: 'funnel',
top: '1%',
bottom: '5%',
left: '2%',
width: '72%',
label: {
position: 'right',
formatter: '{b}'
},
labelLine: {
show: false
},
itemStyle: {
opacity: 0.7
},
data: data.funnelLive.expectedData
},
{
name: 'Actual',
type: 'funnel',
top: '1%',
bottom: '5%',
left: '7%',
width: '62%',
maxSize: '62%',
label: {
position: 'inside',
formatter: '{c}',
color: '#000'
},
itemStyle: {
opacity: 0.4,
borderColor: '#fff',
borderWidth: 1
},
data: data.funnelLive.actualData,
z: 100
}
]
};
}
});
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== ROUTE_PATH) {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = activityId;
this.query();
this.queryFunnel();
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
/* 人数分布排名 */
.monitor-user-count-item {
margin-bottom: 8px;
:deep(.el-progress-bar__outer) {
background: none;
}
.ele-cell-content {
padding-right: 10px;
}
}
.analysis-chart-card-num {
font-size: 30px;
}
.analysis-chart-card-content {
height: 40px;
box-sizing: border-box;
margin-bottom: 12px;
}
.analysis-chart-card-text {
padding-top: 12px;
}
.workplace-table-card {
:deep(.el-table tbody > .el-table__row:last-child td) {
border-bottom: none;
}
:deep(.el-table:before) {
display: none;
}
}
.el-header_add .ele-cell {
color: #409eff;
cursor: pointer;
}
.el-header_add .analysis-chart-card-num {
color: #409eff;
cursor: pointer;
}
.ranking-order-header {
border-bottom: 1px solid var(--border-color-lighter);
box-sizing: border-box;
padding: 10px 17px;
}
</style>
@@ -0,0 +1,196 @@
<!-- 分组编辑弹窗 -->
<template>
<ele-modal
width="680px"
:visible="visible"
:close-on-click-modal="true"
custom-class="ele-dialog-form"
:title="isUpdate ? '修改分组' : '添加分组'"
@update:visible="updateVisible"
>
<el-form ref="form" :model="form" :rules="rules" label-width="82px">
<el-row :gutter="15">
<el-col v-bind="styleResponsive ? { sm: 12 } : { span: 12 }">
<el-form-item label="上级分组:">
<gro-select
v-model="form.parentId"
:data="groupsList"
placeholder="请选择上级分组"
/>
</el-form-item>
<el-form-item label="分组名称:" prop="groupsName">
<el-input
clearable
:maxlength="20"
v-model="form.groupsName"
placeholder="请输入分组名称"
/>
</el-form-item>
<el-form-item label="排序号:">
<el-input-number
:min="0"
:max="99999"
v-model="form.sortNumber"
controls-position="right"
placeholder="请输入排序号"
class="ele-fluid ele-text-left"
/>
</el-form-item>
</el-col>
<el-col v-bind="styleResponsive ? { sm: 12 } : { span: 12 }">
<el-form-item label="用户来源:">
<el-radio-group v-model="form.userFrom">
<el-radio :label="0">机构</el-radio>
<el-radio :label="1">团队</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="form.userFrom == 0 ? '是否门店:' : '是否团队:'">
<el-radio-group v-model="form.ifBiz">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="统计类型:"
v-if="!form.parentId"
prop="statisticsType"
>
<el-select
clearable
v-model="form.statisticsType"
placeholder="请选择统计类型"
class="ele-fluid"
>
<el-option
v-for="item in statisticsTypeList"
:key="item.dictId"
:value="item.dictId"
:label="item.dictName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template v-slot:footer>
<el-button @click="updateVisible(false)">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">
保存
</el-button>
</template>
</ele-modal>
</template>
<script>
import GroSelect from './gro-select.vue';
import { addGroups, updateGroups } from '@/api/sylive/groups';
export default {
components: { GroSelect },
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 上级id
parentId: Number,
// 活动id
activityId: Number,
// 分组数据
groupsList: Array,
// 统计类型数据
statisticsTypeList: Array
},
data() {
const defaultForm = {
groupsId: null,
parentId: null,
groupsName: '',
userFrom: 0,
ifBiz: 0,
statisticsType: null,
sortNumber: null
};
return {
defaultForm,
// 表单数据
form: { ...defaultForm },
// 表单验证规则
rules: {
groupsName: [
{
required: true,
message: '请输入分组名称',
trigger: 'blur'
}
],
statisticsType: [
{
required: true,
message: '请选择统计类型',
trigger: 'blur'
}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
computed: {
// 是否开启响应式布局
styleResponsive() {
return this.$store.state.theme.styleResponsive;
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate((valid) => {
if (!valid) {
return false;
}
this.loading = true;
const data = {
...this.form,
activityId: this.activityId || 0,
parentId: this.form.parentId || 0
};
const saveOrUpdate = this.isUpdate ? updateGroups : addGroups;
saveOrUpdate(data)
.then((msg) => {
this.loading = false;
this.$message.success(msg);
this.updateVisible(false);
this.$emit('done');
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
},
watch: {
visible(visible) {
if (visible) {
if (this.data) {
this.$util.assignObject(this.form, this.data);
this.isUpdate = true;
} else {
this.form.parentId = this.parentId;
this.isUpdate = false;
}
} else {
this.$refs.form.clearValidate();
this.form = { ...this.defaultForm };
}
}
}
};
</script>
@@ -0,0 +1,35 @@
<!-- 分组选择下拉框 -->
<template>
<ele-tree-select
clearable
:value="value || ''"
:data="data"
label-key="groupsName"
value-key="groupsId"
:default-expand-all="true"
:placeholder="placeholder"
@input="updateValue"
/>
</template>
<script>
export default {
props: {
// 选中的数据(v-model)
value: Number,
// 提示信息
placeholder: {
type: String,
default: '请选择分组'
},
// 分组数据
data: Array
},
methods: {
/* 更新选中数据 */
updateValue(value) {
this.$emit('input', value);
}
}
};
</script>
@@ -0,0 +1,336 @@
<template>
<ele-modal
width="70%"
:visible="visible"
title="添加用户"
@update:visible="updateVisible"
>
<div class="ele-body" style="padding: 0px">
<el-card shadow="never" body-style="padding: 0px;">
<el-row :gutter="15">
<el-col v-bind="styleResponsive ? { md: 12 } : { span: 12 }">
<!-- 未选择的用户数据表格 -->
<ele-pro-table
:datasource="unChooseUsers"
:columns="columns"
sub-title=""
height="535px"
emptyText="无未选用户"
:toolkit="[]"
layout="total, prev, pager, next, jumper"
>
<template v-slot:toolkit>
<el-row>
<el-col style="width: 50%; padding-right: 5px">
<ele-tree-select
v-show="userFrom == 0 ? true : false"
:data="organizationList"
label-key="organizationName"
value-key="organizationId"
v-model="organizationIds"
:multiple="true"
:clearable="true"
placeholder="请选择机构"
:disabled="false"
size="small"
:check-strictly="false"
/>
<ele-tree-select
v-show="userFrom == 1 ? true : false"
:data="teamList"
label-key="teamName"
value-key="teamId"
v-model="teamIds"
:multiple="true"
:clearable="true"
placeholder="请选择团队"
:disabled="false"
size="small"
:check-strictly="false"
/>
</el-col>
<el-col style="width: 20%; padding-right: 5px">
<el-input
:maxlength="20"
v-model="keyword"
placeholder="请输入姓名"
size="small"
/>
</el-col>
<el-col style="width: 15%">
<el-button
class="ele-btn-icon"
size="small"
@click="search"
>
查询用户
</el-button>
</el-col>
<el-col style="width: 15%">
<el-button
class="ele-btn-icon"
size="small"
@click="addAll"
>
全部添加
</el-button>
</el-col>
</el-row>
</template>
<template v-slot:action="{ row }">
<el-button size="mini" @click="add(row)">添加</el-button>
</template>
</ele-pro-table>
</el-col>
<el-col v-bind="styleResponsive ? { md: 12 } : { span: 12 }">
<!-- 已选择的用户数据表格 -->
<ele-pro-table
:datasource="chooseUsers"
:columns="columns"
sub-title="已选用户:"
height="535px"
emptyText="未选择用户"
:toolkit="[]"
:tool-style="{ lineHeight: '40px' }"
layout="total, prev, pager, next, jumper"
>
<template v-slot:toolkit>
<el-button
type="primary"
class="ele-btn-icon"
:loading="loading"
size="small"
@click="save"
>
保存已选用户
</el-button>
<el-button
size="small"
type="danger"
plain
class="ele-btn-icon"
@click="removeAll"
>
全部移除
</el-button>
</template>
<template v-slot:action="{ row }">
<el-button type="danger" plain size="mini" @click="remove(row)">
移除
</el-button>
</template>
</ele-pro-table>
</el-col>
</el-row>
</el-card>
</div>
</ele-modal>
</template>
<script>
import { listUsers } from '@/api/sylive/user';
import { listTeamUser } from '@/api/sylive/team-user';
import { addGroupsUser } from '@/api/sylive/groups-user';
export default {
name: 'groupsUserAdd',
props: {
// 全部机构
organizationList: Array,
// 全部团队
teamList: Array,
// 分组id
groupsId: Number,
// 用户来源
userFrom: Number,
// 活动id
activityId: Number,
// 弹窗是否打开
visible: Boolean
},
data() {
return {
organizationIds: [],
teamIds: [],
keyword: null,
// 加载状态
loading: false,
// 全部用户
classes: [],
// 已选择的用户数据
chooseUsers: [],
// 表格列配置
columns: []
};
},
computed: {
/* 未选择的用户数据 */
unChooseUsers() {
return this.classes.filter((d) => this.chooseUsers.indexOf(d) === -1);
},
// 是否开启响应式布局
styleResponsive() {
return this.$store.state.theme.styleResponsive;
}
},
created() {},
methods: {
/* 获取机构用户 */
search() {
if (this.userFrom == 1) {
this.queryTeam();
} else {
this.query();
}
},
/* 获取机构用户 */
query() {
if (this.organizationIds.length <= 0 && !this.keyword) {
this.$message.error('请选择机构或输入姓名');
return;
}
listUsers({
organizationIds: this.organizationIds,
keyword: this.keyword
})
.then((data) => {
this.classes = [];
if (this.chooseUsers.length > 0) {
data.forEach((item) => {
let choose = false;
this.chooseUsers.forEach((d) => {
if (item.userId == d.userId) {
choose = true;
}
});
if (!choose) {
this.classes.push(item);
}
});
} else {
this.classes = data;
}
})
.catch((e) => {
this.$message.error(e.message);
});
},
/* 获取团队用户 */
queryTeam() {
if (this.teamIds.length <= 0 && !this.keyword) {
this.$message.error('请选择团队或输入姓名');
return;
}
listTeamUser({
teamIds: this.teamIds,
keyword: this.keyword
})
.then((data) => {
this.classes = [];
if (this.chooseUsers.length > 0) {
data.forEach((item) => {
let choose = false;
this.chooseUsers.forEach((d) => {
if (item.userId == d.userId) {
choose = true;
}
});
if (!choose) {
this.classes.push(item);
}
});
} else {
this.classes = data;
}
})
.catch((e) => {
this.$message.error(e.message);
});
},
/* 添加 */
add(row) {
this.chooseUsers.push(row);
},
/* 移除 */
remove(row) {
this.chooseUsers.splice(this.chooseUsers.indexOf(row), 1);
},
/* 添加全部 */
addAll() {
this.unChooseUsers.forEach((d) => {
this.chooseUsers.push(d);
});
},
/* 移除所有 */
removeAll() {
this.chooseUsers.splice(0, this.chooseUsers.length);
},
save() {
if (this.chooseUsers.length <= 0) {
this.$message.error('未选择用户');
return;
}
addGroupsUser({
groupsId: this.groupsId,
userFrom: this.userFrom,
activityId: this.activityId,
chooseUsers: this.chooseUsers
})
.then((msg) => {
this.loading = false;
this.$message.success(msg);
this.updateVisible(false);
this.$emit('done');
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
},
watch: {
visible(visible) {
this.columns = [
{
columnKey: 'action',
label: '操作',
width: 100,
align: 'center',
slot: 'action'
},
{
prop: 'uname',
label: '姓名',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'mobile',
label: '手机号',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: this.userFrom == 0 ? 'organizationName' : 'teamName',
label: this.userFrom == 0 ? '机构' : '团队',
showOverflowTooltip: true,
minWidth: 110
}
];
if (visible) {
//
} else {
this.organizationIds = [];
this.teamIds = [];
this.keyword = null;
this.classes = [];
this.chooseUsers = [];
}
}
}
};
</script>
@@ -0,0 +1,180 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
width="680px"
:visible="visible"
:close-on-click-modal="true"
custom-class="ele-dialog-form"
:title="isUpdate ? '修改用户' : '添加用户'"
@update:visible="updateVisible"
>
<el-form ref="form" :model="form" :rules="rules" label-width="82px">
<el-row :gutter="15">
<el-col v-bind="styleResponsive ? { sm: 12 } : { span: 12 }">
<el-form-item label="所属机构:" prop="organizationId">
<gro-select
:data="organizationList"
placeholder="请选择所属机构"
v-model="form.organizationId"
/>
</el-form-item>
<el-form-item label="手机号:" prop="mobile">
<el-input
clearable
:maxlength="11"
:disabled="isUpdate"
v-model="form.mobile"
placeholder="请输入手机号"
/>
</el-form-item>
</el-col>
<el-col v-bind="styleResponsive ? { sm: 12 } : { span: 12 }">
<el-form-item label="姓名:" prop="uname">
<el-input
clearable
:maxlength="20"
v-model="form.uname"
placeholder="请输入姓名"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template v-slot:footer>
<el-button @click="updateVisible(false)">取消</el-button>
<el-button type="primary" :loading="loading" @click="save">
保存
</el-button>
</template>
</ele-modal>
</template>
<script>
import { phoneReg } from 'ele-admin';
import GroSelect from './gro-select.vue';
import { addUser, updateUser, checkExistence } from '@/api/sylive/user';
export default {
components: { GroSelect },
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 全部机构
organizationList: Array,
// 机构id
organizationId: Number
},
data() {
const defaultForm = {
userId: null,
organizationId: null,
mobile: '',
uname: ''
};
return {
defaultForm,
// 表单数据
form: { ...defaultForm },
// 表单验证规则
rules: {
mobile: [
{
required: true,
trigger: 'blur',
validator: (_rule, value, callback) => {
if (!value) {
return callback(new Error('请输入手机号'));
}
const st = new RegExp(phoneReg);
if (!st.test(value)) {
return callback(new Error('手机号格式不正确'));
}
checkExistence('mobile', value, this.data?.mobile)
.then(() => {
callback(new Error('手机号已经存在'));
})
.catch(() => {
callback();
});
}
}
],
organizationId: [
{
required: true,
message: '请选择所属机构',
trigger: 'blur'
}
],
uname: [
{
required: true,
message: '请输入姓名',
trigger: 'blur'
}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
computed: {
// 是否开启响应式布局
styleResponsive() {
return this.$store.state.theme.styleResponsive;
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate((valid) => {
if (!valid) {
return false;
}
this.loading = true;
const data = {
...this.form
};
const saveOrUpdate = this.isUpdate ? updateUser : addUser;
saveOrUpdate(data)
.then((msg) => {
this.loading = false;
this.$message.success(msg);
this.updateVisible(false);
this.$emit('done');
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
},
watch: {
visible(visible) {
if (visible) {
if (this.data) {
this.$util.assignObject(this.form, {
...this.data
});
this.isUpdate = true;
} else {
this.form.organizationId = this.organizationId;
this.isUpdate = false;
}
} else {
this.$refs.form.clearValidate();
this.form = { ...this.defaultForm };
}
}
}
};
</script>
@@ -0,0 +1,223 @@
<template>
<div>
<!-- 数据表格 -->
<ele-pro-table
ref="table"
:columns="columns"
:datasource="datasource"
height="calc(100vh - 265px)"
full-height="calc(100vh - 116px)"
tool-class="ele-toolbar-form"
cache-key="systemOrgUserTable"
>
<!-- 表头工具栏 -->
<template v-slot:toolbar>
<gro-user-search @search="reload">
<el-button
size="small"
type="primary"
icon="el-icon-plus"
class="ele-btn-icon"
@click="openAdd()"
>
添加
</el-button>
</gro-user-search>
</template>
<!-- 角色列 -->
<template v-slot:roleName="{ row }">
<el-tag type="primary" size="mini">
{{ row.roleName }}
</el-tag>
</template>
<!-- 状态列 -->
<template v-slot:status="{ row }">
<el-switch
:active-value="0"
:inactive-value="1"
v-model="row.status"
@change="editStatus(row)"
/>
</template>
<!-- 操作列 -->
<template v-slot:action="{ row }">
<el-popconfirm
class="ele-action"
title="确定要删除此用户吗?"
@confirm="remove(row)"
>
<template v-slot:reference>
<el-link type="danger" :underline="false" icon="el-icon-delete">
删除
</el-link>
</template>
</el-popconfirm>
</template>
</ele-pro-table>
<!-- 编辑弹窗 -->
<gro-user-add
:visible.sync="showAdd"
:user-from="userFrom"
:groups-id="groupsId"
:activity-id="activityId"
:organization-list="organizationList"
:team-list="teamList"
@done="reload"
/>
</div>
</template>
<script>
import GroUserSearch from './gro-user-search.vue';
import GroUserAdd from './gro-user-add.vue';
import {
pageGroupsUser,
removeGroupsUser,
updateGroupsUserStatus
} from '@/api/sylive/groups-user';
export default {
components: { GroUserSearch, GroUserAdd },
props: {
// 分组id
groupsId: Number,
// 用户来源
userFrom: Number,
// 活动id
activityId: Number,
// 全部分组
groupsList: Array,
// 全部团队
teamList: Array,
// 全部机构
organizationList: Array
},
data() {
return {
// 表格列配置
columns: [
{
columnKey: 'index',
type: 'index',
width: 45,
align: 'center',
showOverflowTooltip: true,
fixed: 'left'
},
{
prop: 'uname',
label: '姓名',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'nickname',
label: '微信昵称',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'mobile',
label: '手机号',
showOverflowTooltip: true,
minWidth: 110
},
{
prop: 'roleName',
label: '角色',
showOverflowTooltip: true,
minWidth: 70,
slot: 'roleName'
},
{
prop: 'createTime',
label: '创建时间',
showOverflowTooltip: true,
minWidth: 120,
formatter: (_row, _column, cellValue) => {
return this.$util.toDateString(cellValue);
}
},
{
prop: 'status',
label: '状态',
align: 'center',
width: 110,
resizable: false,
slot: 'status'
},
{
columnKey: 'action',
label: '操作',
width: 120,
align: 'center',
resizable: false,
slot: 'action',
showOverflowTooltip: true
}
],
// 当前编辑数据
current: null,
// 是否显示添加弹窗
showAdd: false,
// 是否显示编辑弹窗
showEdit: false
};
},
methods: {
/* 表格数据源 */
datasource({ page, limit, where, order }) {
return pageGroupsUser({
...where,
...order,
page,
limit,
groupsId: this.groupsId,
activityId: this.activityId
});
},
/* 刷新表格 */
reload(where) {
this.$refs.table.reload({ page: 1, where: where });
},
/* 显示添加 */
openAdd() {
this.showAdd = true;
},
/* 删除 */
remove(row) {
const loading = this.$loading({ lock: true });
removeGroupsUser(row.groupsUserId)
.then((msg) => {
loading.close();
this.$message.success(msg);
this.reload();
})
.catch((e) => {
loading.close();
this.$message.error(e.message);
});
},
/* 更改状态 */
editStatus(row) {
const loading = this.$loading({ lock: true });
updateGroupsUserStatus(row.groupsUserId, row.status)
.then((msg) => {
loading.close();
this.$message.success(msg);
})
.catch((e) => {
loading.close();
row.status = !row.status ? 1 : 0;
this.$message.error(e.message);
});
}
},
watch: {
// 监听机构id变化
groupsId() {
this.reload();
}
}
};
</script>
@@ -0,0 +1,79 @@
<!-- 搜索表单 -->
<template>
<el-form
size="small"
class="ele-form-search"
@keyup.enter.native="search"
@submit.native.prevent
>
<el-row :gutter="10">
<el-col v-bind="styleResponsive ? { md: 8 } : { span: 8 }">
<el-form-item>
<el-input
clearable
size="small"
v-model="where.uname"
placeholder="请输入姓名"
/>
</el-form-item>
</el-col>
<el-col v-bind="styleResponsive ? { md: 8 } : { span: 8 }">
<el-form-item>
<el-input
clearable
size="small"
v-model="where.nickname"
placeholder="请输入微信昵称"
/>
</el-form-item>
</el-col>
<el-col v-bind="styleResponsive ? { md: 8 } : { span: 8 }">
<el-form-item>
<el-button
size="small"
type="primary"
icon="el-icon-search"
class="ele-btn-icon"
@click="search"
>
查询
</el-button>
<slot></slot>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
data() {
// 默认表单数据
const defaultWhere = {
uname: '',
nickname: ''
};
return {
// 表单数据
where: { ...defaultWhere }
};
},
computed: {
// 是否开启响应式布局
styleResponsive() {
return this.$store.state.theme.styleResponsive;
}
},
methods: {
/* 搜索 */
search() {
this.$emit('search', this.where);
},
/* 重置 */
reset() {
this.where = { ...this.defaultWhere };
this.search();
}
}
};
</script>
+258
View File
@@ -0,0 +1,258 @@
<template>
<div class="ele-body">
<el-card shadow="never" v-loading="loading">
<ele-split-layout
width="266px"
allow-collapse
:right-style="{ overflow: 'hidden' }"
>
<div>
<!-- 操作按钮 -->
<ele-toolbar class="ele-toolbar-actions">
<div style="margin: 5px 0">
<el-button
size="small"
type="primary"
icon="el-icon-plus"
class="ele-btn-icon"
@click="openEdit()"
>
添加
</el-button>
<el-button
size="small"
type="warning"
icon="el-icon-edit"
class="ele-btn-icon"
:disabled="!current"
@click="openEdit(current)"
>
修改
</el-button>
<el-button
size="small"
type="danger"
icon="el-icon-delete"
class="ele-btn-icon"
:disabled="!current"
@click="remove"
>
删除
</el-button>
</div>
</ele-toolbar>
<div class="ele-border-lighter sys-groups-list">
<el-tree
ref="tree"
:data="data"
highlight-current
node-key="groupsId"
:props="{ label: 'groupsName' }"
:expand-on-click-node="false"
:default-expand-all="true"
@node-click="onNodeClick"
/>
</div>
</div>
<template v-slot:content>
<gro-user-list
v-if="current"
:user-from="current.userFrom"
:groups-id="current.groupsId"
:activity-id="activityId"
:organization-list="organizationList"
:team-list="teamList"
/>
</template>
</ele-split-layout>
</el-card>
<!-- 编辑弹窗 -->
<gro-edit
:visible.sync="showEdit"
:data="editData"
:activity-id="activityId"
:parent-id="parentId"
:groups-list="data"
:statistics-type-list="statisticsTypeList"
@done="query"
/>
</div>
</template>
<script>
import GroUserList from './components/gro-user-list.vue';
import GroEdit from './components/gro-edit.vue';
import { listGroups, removeGroups } from '@/api/sylive/groups';
import { listOrganizations } from '@/api/sylive/organization';
import { listTeams } from '@/api/sylive/team';
import { listDictionaries } from '@/api/system/dictionary';
const ROUTE_PATH = '/sylive/groups';
export default {
name: 'SyliveGroups',
components: { GroUserList, GroEdit },
data() {
return {
// 加载状态
loading: true,
// 列表数据
data: [],
// 选中数据
current: null,
// 机构数据
organizationList: [],
// 团队数据
teamList: [],
// 统计类型数据
statisticsTypeList: [],
// 是否显示表单弹窗
showEdit: false,
// 编辑回显数据
editData: null,
// 上级id
parentId: null,
// 活动id
activityId: null
};
},
created() {
this.organizationQuery();
this.teamQuery();
this.statisticsTypeQuery();
},
methods: {
/* 查询 */
query() {
this.loading = true;
listGroups({ activityId: this.activityId })
.then((list) => {
this.loading = false;
this.data = this.$util.toTreeData({
data: list,
idField: 'groupsId',
parentIdField: 'parentId'
});
this.$nextTick(() => {
this.onNodeClick(this.data[0]);
});
})
.catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
},
/* 查询机构 */
organizationQuery() {
listOrganizations({
unOrganizationType: 4,
activityId: this.activityId
})
.then((list) => {
this.organizationList = this.$util.toTreeData({
data: list,
idField: 'organizationId',
parentIdField: 'parentId'
});
})
.catch((e) => {
this.$message.error(e.message);
});
},
/* 查询团队 */
teamQuery() {
listTeams({ unTeamType: 3 })
.then((list) => {
this.teamList = this.$util.toTreeData({
data: list,
idField: 'teamId',
parentIdField: 'parentId'
});
})
.catch((e) => {
this.$message.error(e.message);
});
},
/* 查询统计类型 */
statisticsTypeQuery() {
listDictionaries({ dictCode: 'groups_level' })
.then((list) => {
this.statisticsTypeList = list;
})
.catch((e) => {
this.$message.error(e.message);
});
},
/* 选择数据 */
onNodeClick(row) {
if (row) {
this.current = row;
this.parentId = row.groupsId;
this.$refs.tree.setCurrentKey(row.groupsId);
} else {
this.current = null;
this.parentId = null;
}
},
/* 显示编辑 */
openEdit(item) {
this.editData = item;
this.showEdit = true;
},
/* 删除 */
remove() {
this.$confirm('确定要删除选中的分组吗?', '提示', {
type: 'warning'
})
.then(() => {
const loading = this.$loading({ lock: true });
removeGroups(this.current.groupsId)
.then((msg) => {
loading.close();
this.$message.success(msg);
this.query();
})
.catch((e) => {
loading.close();
this.$message.error(e.message);
});
})
.catch(() => {});
}
},
watch: {
$route: {
handler(route) {
const { path } = route;
if (path !== ROUTE_PATH) {
return;
}
const activityId = this.$route.query.id;
if (!activityId || activityId == this.activityId) {
return;
}
this.activityId = Number(activityId);
this.query();
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
.sys-groups-list {
height: calc(100vh - 264px);
box-sizing: border-box;
border-width: 1px;
border-style: solid;
overflow: auto;
}
.sys-groups-list :deep(.el-tree-node__content) {
height: 40px;
& > .el-tree-node__expand-icon {
margin-left: 10px;
}
}
</style>