Files
tk-page-Fan/src/views/hosts/hostsList.vue
pengxiaolong 73d8b38f99 优化代码
2025-09-25 21:23:42 +08:00

933 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="hostList">
<div>
<!-- -->
<div style="display: flex">
<el-checkbox
class="isFilter"
v-model="queryFormData.isFilter"
label="过滤隐私用户"
size="large"
border
/>
<el-input
v-model="queryFormData.coinMin"
placeholder="最小金币"
size="large"
style="width: 180px"
type="number"
:disabled = "streamdialogVisibletext || isRunnings"
/>
<el-input
v-model="queryFormData.coinMax"
placeholder="最大金币"
size="large"
style="width: 180px"
class="right-input"
type="number"
:disabled = "streamdialogVisibletext || isRunnings"
/>
<el-input
v-model="queryFormData.levelMin"
placeholder="最小等级"
size="large"
style="width: 180px"
class="right-input"
type="number"
:disabled = "streamdialogVisibletext || isRunnings"
/>
<el-input
v-model="queryFormData.levelMax"
placeholder="最大等级"
size="large"
style="width: 180px"
class="right-input"
type="number"
:disabled = "streamdialogVisibletext || isRunnings"
/>
<el-button
@click="streamdialogVisible = true"
:disabled="isRunnings"
class="put-button buttoMore-filters Specify"
type="primary"
>
<div>{{ streamdialogVisibletext ? '已指定直播间' : '指定直播间' }}</div></el-button
>
<div
class="right-input right-text"
style="width: 160px; height: 50px; line-height: 50px"
>
总数{{ getBrotherInfodata.total }}
</div>
<div
class="right-input right-text"
style="width: 160px; height: 50px; line-height: 50px"
>
有效数{{ getBrotherInfodata.valid }}
</div>
<el-button class="serch-button right-input" type="primary" @click="Resetss"
>重置</el-button
>
<el-button
v-show="queryFormData.isRunning"
class="serch-button right-input"
type="primary"
@click="getBigBrother"
>开始</el-button
>
<el-button
v-show="!queryFormData.isRunning"
class="serch-button right-input"
type="primary"
@click="BigBrotherstop"
>结束</el-button
>
</div>
<!-- `````````````````````````````````````````````````````````````````````````````````````````````````` -->
<div style="width: 100%; border-bottom: 1px solid #e9e9e9; margin-top: 30px"></div>
<!-- ···································································································· -->
<div style="display: flex; margin-top: 30px; flex-wrap: wrap">
<el-select
v-model="searchForm.region"
filterable
placeholder="选择国家"
size="large"
style="width: 160px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchForm.displayId"
placeholder="大哥id"
size="large"
style="width: 160px"
class="right-input"
clearable
/>
<el-button class="serch-button right-input" type="primary" @click="serch"
>查询</el-button
>
<el-button class="serch-button" type="primary" @click="reset"> 重置 </el-button>
<el-button
class="put-button"
:disabled="tableData.length == 0"
type="primary"
@click="exportList"
>导出Excel数据</el-button
>
<el-button
@click="filterdialogVisible = true"
class="put-button buttoMore-filters"
type="primary"
><img class="filters-img" src="@/assets/filter.png" />
<div style="margin-left: 10px">更多筛选</div></el-button
>
<el-button
class="serch-button right-input"
style="width: 150px"
type="primary"
@click="openTikTok"
>打开 TikTok 登录</el-button
>
<div
class="right-input right-text"
style="width: auto; height: 50px; line-height: 50px"
>
当前网络{{ countryData }}
</div>
<div class="right-input right-text" style="width: auto; height: 50px; line-height: 50px">
运行时间{{ String(hourstuo).padStart(2, '0') }}:{{ String(minutestuo).padStart(2, '0') }}:{{ String(secondstuo).padStart(2, '0') }}
</div>
</div>
<!-- ····················································································································· -->
<div class="hostTable center-justify">
<el-table
ref="multipleTableRef"
:data="tableData"
stripe
v-loading="loading"
max-height="500"
@cell-dblclick="handleCellDbClick"
@selection-change="handleSelectionChange"
>
<!-- <el-table-column type="selection" width="35" /> -->
<el-table-column fixed prop="displayId" label="Id" :width="screenWidth">
<template #default="scope">
<div class="hostIdText" @click="openHTML(scope.row.displayId)">
{{ scope.row.displayId }}
</div>
</template>
</el-table-column>
<el-table-column
v-for="label in labelList"
:key="label.paramCode"
:prop="label.paramCode"
:label="label.paramCodeMeaning"
:width="screenWidth"
>
<template v-if="label.paramCode != 'createDt'" #default="scope"> </template>
</el-table-column>
</el-table>
</div>
<!-- ······································································································································ -->
<!-- 分页 -->
<div class="center-justify" style="margin-top: 30px">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
background
layout="sizes, prev, pager, next"
:total="total"
:page-sizes="[10, 20, 50, 100, 500, 1000]"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- ·······································································弹窗······································································· -->
<el-dialog v-model="filterdialogVisible" width="800px" :before-close="handleClose">
<el-row :gutter="20">
<el-col :span="4">
<!-- <label>选择筛选条件</label> -->
<div style="height: 100%; padding-top: 10px" class="center-justify">时间</div>
</el-col>
<el-col :span="10">
<div><label>开始时间/结束时间</label></div>
<el-date-picker
v-model="createTimes"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择查询时间"
size="large"
style="width: 600px; margin-top: 10px"
/>
</el-col>
</el-row>
<el-row
v-for="(field, index) in fields"
:key="index"
:gutter="20"
style="margin-bottom: 10px"
>
<el-col :span="4">
<div style="height: 100%" class="center-justify">
{{ field.label }}
</div>
</el-col>
<el-col :span="10">
<div><label>最小值</label></div>
<el-input
type="number"
:oninput="'if(value.length>9)value=value.slice(0,9)'"
v-model.number="searchForm[field.minModel]"
placeholder="请输入最小值"
/>
</el-col>
<el-col :span="10">
<div><label>最大值</label></div>
<el-input
type="number"
:oninput="'if(value.length>9)value=value.slice(0,9)'"
v-model.number="searchForm[field.maxModel]"
placeholder="请输入最大值"
/>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<!-- <label>选择筛选条件</label> -->
<div style="height: 100%; padding-top: 10px" class="center-justify">排序</div>
</el-col>
<el-col :span="10">
<div><label>排序类型</label></div>
<el-select
v-model="sortData.sortName"
filterable
placeholder="请选择"
style="width: 240px"
>
<el-option
v-for="item in sortNameOptions"
:key="item.type"
:label="item.label"
:value="item.type"
/>
</el-select>
</el-col>
<el-col :span="10">
<div><label>升序/降序</label></div>
<el-select
v-model="sortData.sort"
filterable
placeholder="请选择"
style="width: 240px"
>
<el-option
v-for="item in [
{ label: '升序', value: 'asc' },
{ label: '降序', value: 'desc' },
]"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="reset"> 重置 </el-button>
<!-- <el-button @click="filterdialogVisible = false">取消</el-button> -->
<el-button type="primary" @click="handelClick"> 确认 </el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="streamdialogVisible" width="800px" :before-close="handleClose">
<div class="specify-dialog">
<el-input
v-model="textarea"
style="width: 100%"
:rows="15"
type="textarea"
placeholder="请输入直播间id多个id用回车键隔开"
@input="handleInput"
/>
<div class="specify-footer">
<el-button class="specify-button" @click="specifyCancel">取消指定直播间</el-button>
<el-button class="specify-button" type="primary" @click="specifyreset"> 重置 </el-button>
<el-button class="specify-button" type="primary" @click="specifyClick"> 确认 </el-button>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
<script setup>
// import { getToken, setToken, removeToken } from '@/utils/storage'
import {
tkhostdata,
dicts,
tkhostdetail,
downList,
getStaffList,
managerhosts,
upholdinfo,
getCountryinfo,
accountName,
} from "@/api/account";
import { usePythonBridge } from "@/utils/pythonBridge";
import { getUser, setSerch, getSerch } from "@/utils/storage";
import { ref, reactive, onMounted, onBeforeUnmount } from "vue";
const hourstuo = ref(0);
const minutestuo = ref(0);
const secondstuo = ref(0);
import EChartsComponent from "@/components/EChartsComponent.vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { color } from "echarts";
import { getCountryName } from "@/utils/countryUtil";
import { ElLoading } from "element-plus";
//复制单元格内容
function handleCellDbClick(row, column, cell, event) {
const text = cell?.textContent?.trim();
if (!text) {
ElMessage({
type: "warning",
message: "无内容可复制",
});
return;
}
copyToClipboard(text);
}
//复制到剪切板
async function copyToClipboard(text) {
try {
// 尝试使用现代Clipboard API
if (navigator.clipboard) {
await navigator.clipboard.writeText(text);
ElMessage({
type: "success",
message: "复制成功",
});
return;
}
// 备用方法使用document.execCommand
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
if (document.execCommand('copy')) {
ElMessage({
type: "success",
message: "复制成功",
});
} else {
throw new Error('execCommand failed');
}
document.body.removeChild(textarea);
} catch (err) {
console.error('复制失败:', err);
ElMessage({
type: "error",
message: "复制失败",
});
}
}
//ip国家
let countryData = ref("");
//获取屏幕宽度
const screenWidth = ref(window.innerWidth / 11);
//获取国家
const getIpInfo = async () => {
try {
const response = await fetch("https://ipapi.co/json/");
if (!response.ok) {
throw new Error("请求失败");
}
const data = await response.json();
console.log("IP信息:", data.country);
countryData.value = getCountryName(data.country);
} catch (error) {
console.error("请求出错:", error);
ElMessageBox.prompt("请输入将要获取国家的中文名", "获取国家失败", {
confirmButtonText: "确认",
cancelButtonText: "取消",
showClose: false,
closeOnClickModal: false,
showCancelButton: false,
}).then(({ value }) => {
countryData.value = value;
});
// .catch(() => {
// ElMessage({
// type: 'info',
// message: 'Input canceled',
// })
// })
}
};
//打开TikTok登录
function openTikTok() {
loginTikTok();
}
//重置
function Resetss() {
queryFormData.value = {};
}
//大哥climb
const queryFormData = ref({
coinMin: "",
coinMax: "",
levelMin: "",
levelMax: "",
isFilter: false,
isRunning: true,
anchor_ids: [],
});
//时间
const timerId = ref(null);
const getBrotherInfodata = ref({
total: 0,
valid: 0,
});
//结束爬取
function BigBrotherstop() {
const loading = ElLoading.service({
lock: true,
text: "正在停止...",
background: "rgba(0, 0, 0, 0.7)",
});
stopTimerfun();
isRunnings.value = false;
queryFormData.value.tenantId = userInfo.value.tenantId;
queryFormData.value.region = countryData.value;
controlTask(JSON.stringify(queryFormData.value)).then((res) => {
queryFormData.value.isRunning = true;
clearInterval(timerId.value);
timerId.value = null;
loading.close();
});
Specifystreaming(JSON.stringify(queryFormData.value)).then((res) => {
queryFormData.value.isRunning = true;
clearInterval(timerId.value);
timerId.value = null;
loading.close();
});
}
//指定直播间
function specifyClick() {
if (textarea.value == "") {
ElMessage({
type: "error",
message: "请输入直播间id",
});
return;
}
setStorageStreamId(textarea.value).then((res) => {
});
queryFormData.value.anchor_ids = textarea.value.split("\n");
streamdialogVisible.value = false;
streamdialogVisibletext.value = true;
}
//指定直播间重置
function specifyreset() {
textarea.value = "";
streamdialogVisibletext.value = false;
queryFormData.value.anchor_ids = []
}
//指定直播间取消
function specifyCancel() {
streamdialogVisible.value = false;
streamdialogVisibletext.value = false;
queryFormData.value.anchor_ids = []
}
//输入框input
function handleInput() {
streamdialogVisibletext.value = false;
}
//获取主播列表
//开始爬取
function getBigBrother() {
const loading = ElLoading.service({
lock: true,
text: "正在启动...",
background: "rgba(0, 0, 0, 0.7)",
});
queryFormData.value.tenantId = userInfo.value.tenantId;
queryFormData.value.region = countryData.value;
startTimerfun();
isRunnings.value = true;
if (queryFormData.value.anchor_ids == [] || queryFormData.value.anchor_ids == null || queryFormData.value.anchor_ids == undefined || queryFormData.value.anchor_ids.length == "") {
controlTask(JSON.stringify(queryFormData.value)).then((res) => {
queryFormData.value.isRunning = false;
timerId.value = setInterval(() => {
getBrotherInfo().then((res) => {
loading.close();
getBrotherInfodata.value = res;
});
}, 1000);
});
} else {
Specifystreaming(JSON.stringify(queryFormData.value)).then((res) => {
queryFormData.value.isRunning = false;
timerId.value = setInterval(() => {
getBrotherInfo().then((res) => {
loading.close();
getBrotherInfodata.value = res;
});
}, 1000);
});
}
}
const loading = ref(false);
//py方法
const {
givePyAnchorId,
exportToExcel,
loginTikTok,
controlTask,
getBrotherInfo,
Specifystreaming,
setStorageStreamId,
getStorageStreamId
} = usePythonBridge();
let num = ref(0);
//账号信息
const userInfo = ref({});
//主播列表DOM
const multipleTableRef = ref(null);
let labelList = ref([
{ paramCode: "userIdStr", paramCodeMeaning: "用户id" },
{ paramCode: "level", paramCodeMeaning: "等级" },
{ paramCode: "fansLevel", paramCodeMeaning: "粉丝团等级" },
{ paramCode: "hostcoins", paramCodeMeaning: "打赏的金币" },
{ paramCode: "hostDisplayId", paramCodeMeaning: "所在直播间主播id" },
{ paramCode: "region", paramCodeMeaning: "地区" },
{ paramCode: "followerCount", paramCodeMeaning: "粉丝数" },
{ paramCode: "followingCount", paramCodeMeaning: "关注数" },
{ paramCode: "createTime", paramCodeMeaning: "创建时间" },
]);
const tableData = ref([]);
//主播列表传参
const searchForm = ref({});
const createTimes = ref([]);
//分页
const page = ref(1);
const pageSize = ref(10);
const fields = [
// { label: "打赏的金币", minModel: "hostcoinsMin", maxModel: "hostcoinsMax" },
{ label: "等级", minModel: "levelMin", maxModel: "levelMax" },
];
//排序
let sortData = ref({ sortName: "createTime", sort: "desc" });
//排序类型
let sortNameOptions = ref([
{ label: "创建时间", type: "createTime" },
{ label: "打赏的金币", type: "hostsCoins" },
{ label: "打赏金币总和", type: "totalGiftCoins" },
{ label: "等级", type: "level" },
]);
//是否在运行
let isRunnings = ref(false);
//是否指定直播间
let streamdialogVisibletext = ref(false);
//指定直播间弹窗
let streamdialogVisible = ref(false);
//指定直播间id
let textarea = ref("");
//计时器
let startTime = ref(null);
//分配的员工
let staffId = ref({});
//备注信息
let commentInfo = ref("");
//备注信息主播
let commentHost = ref("");
//分页
let total = ref(0);
//是否渲染
const isPopoverVisible = reactive({});
let options = ref([]);
let version = ref("0.0.0");
onMounted(() => {
window.addEventListener("resize", handleResize);
setTimeout(() => {
getUserdata();
getStorageStreamId(1).then((res) => {
textarea.value = res == "" ? "" : JSON.parse(res);
});
}, 500);
});
// 启动计时器
function startTimerfun() {
startTime.value = setInterval(() => {
secondstuo.value++;
if (secondstuo.value >= 60) {
secondstuo.value = 0;
minutestuo.value++;
if (minutestuo.value >= 60) {
minutestuo.value = 0;
hourstuo.value++;
}
}
}, 1000);
}
// 停止计时器
function stopTimerfun() {
clearInterval(startTime.value);
}
//表格单元格长度判断
function handleResize() {
screenWidth.value = window.innerWidth / 11;
}
async function getUserdata() {
const User = await getUser();
userInfo.value = User;
getCountry(); //获取国家
getlist(); //获取主播列表
getIpInfo();
}
function serch() {
page.value = 1;
getlist();
}
function exportList() {
exportToExcel(requestParams.value);
}
//分页每页条数
function handleSizeChange(val) {
console.log(`${val} items per page`);
getlist();
}
//分页页数
function handleCurrentChange(val) {
console.log(`current page: ${val}`);
getlist();
}
//选择行
function handleSelectionChange(data) {
console.log(data);
selectHostList.value = [];
data.forEach((item) => {
selectHostList.value.push(item.hostId);
});
// multipleTableRef.value = data
// console.log(multipleTableRef.value)
}
//时间格式化
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从 0开始需要+1
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
//获取主播列表
//记录请求参数
let requestParams = ref({});
const getlist = () => {
loading.value = true;
tkhostdata({
tenantId: Number(userInfo.value.tenantId),
sort: sortData.value.sort, //正序倒序
sortName: sortData.value.sortName, //排序类型
current: page.value,
pageSize: pageSize.value,
createTimeStart: createTimes.value[0],
createTimeEnd: createTimes.value[1],
...searchForm.value, //筛选条件
}).then((res) => {
loading.value = false;
console.log(res);
if (res) {
requestParams.value = res.records;
console.log(res.records);
total.value = Number(res.total);
tableData.value = res.records.map((item) => ({
level: item.level, // 等级
fansLevel: item.fansLevel, // 粉丝团等级
createTime: formatDate(new Date(item.createTime)), // 创建时间
followerCount: item.followerCount, // 粉丝数
followingCount: item.followingCount, // 关注数
hostDisplayId: item.hostDisplayId, // 所在直播间主播id
hostcoins: item.hostcoins, // 打赏的金币
region: item.region, // 地区
totalGiftCoins: item.totalGiftCoins, // 打赏金币总和
userIdStr: item.userIdStr, // 用户id
displayId: item.displayId,
}));
}
});
};
function handelClick() {
filterdialogVisible.value = false;
}
function reset() {
searchForm.value = {};
sortData.value = { sortName: "createTime", sort: "desc" };
createTimes.value = [];
}
function handleClose(done) {
console.log("关闭");
done();
}
function filterTag(value, row) {
console.log(row.useable, value);
return row.useable === value;
}
//获取国家
function getCountry() {
getCountryinfo({})
.then((res) => {
res.forEach((item) => {
if (item.countryGroupName) {
options.value.push({
value: item.countryGroupName,
label: item.countryGroupName,
});
}
});
})
.catch((err) => {
console.log("getCountry", err);
});
}
function openPopover(hostId, paramCode) {
isPopoverVisible[`${hostId}-${paramCode}`] = true;
}
function closePopover(hostId, paramCode) {
// isPopoverVisible[`${hostId}-${paramCode}`] = false;
}
function openHTML(id) {
console.log(id);
givePyAnchorId(id);
}
</script>
<style lang="less">
.hostList {
box-sizing: border-box;
// height: 100vh;
padding: 40px;
/* 页面无法选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.hostTable {
width: 100%;
padding: 30px 0;
.hostIdText {
text-decoration: underline;
cursor: pointer;
}
}
.serch-button {
width: 80px;
height: 47px;
background: @btn-bg-color;
border-radius: 10px;
border: none;
}
.put-button {
width: 132px;
height: 47px;
background: @btn-bg-color;
border-radius: 10px;
border: none;
}
.Specify {
margin-left: 20px;
}
}
.buttoMore-filters {
width: 150px;
}
.filters-img {
width: 30px;
}
.el-dialog {
--el-dialog-font-line-height: 50px;
--el-dialog-width: 600px;
--el-dialog-border-radius: 8px;
// border: 10px solid @bg-color-light;
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
// justify-content: center;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
.center-align {
display: flex;
justify-content: space-between;
}
.center-flex {
display: flex;
justify-content: center;
align-items: center;
}
</style>
<style scoped lang="less">
::v-deep(.el-input__wrapper) {
background-color: #f2faf9;
border: 1px solid @bg-color;
height: 44px;
}
::v-deep(.el-select__wrapper) {
background-color: #f2faf9;
border: 1px solid @bg-color;
height: 48px;
}
::v-deep(.el-pagination.is-background .el-pager li.is-active) {
background-color: @bg-color;
}
.right-input {
margin-left: 20px;
}
.isFilter {
margin-right: 20px;
height: 47px;
}
.right-text {
color: #666;
font-size: 16px;
}
.specify-dialog {
height: 400px;
}
.specify-footer{
width: 100%;
display: flex;
justify-content:space-around;
margin-top: 20px;
}
.specify-button{
width: 200px;
height: 50px;
}
</style>