feat(yuntu): update crowd portrait download script
This commit is contained in:
parent
c07df0cfe1
commit
735dd3ecf9
@ -1,286 +0,0 @@
|
|||||||
// ==UserScript==
|
|
||||||
// @name 批量下载人群画像
|
|
||||||
// @namespace https://bbs.tampermonkey.net.cn/
|
|
||||||
// @version 0.3.2
|
|
||||||
// @description try to take over the world!
|
|
||||||
// @author wan喜
|
|
||||||
// @match https://yuntu.oceanengine.com/yuntu_ng/analysis/audience/list?*
|
|
||||||
// @match https://yuntu.oceanengine.com/yuntu_brand/analysis/audience/list?*
|
|
||||||
// @icon https://www.google.com/s2/favicons?domain=oceanengine.com
|
|
||||||
// @require https://cdn.staticfile.net/sweetalert2/11.10.0/sweetalert2.all.min.js
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
"use strict"
|
|
||||||
|
|
||||||
var newButton = document.createElement("button")
|
|
||||||
newButton.innerHTML = "下载画像"
|
|
||||||
newButton.type = "button"
|
|
||||||
newButton.style.height = "34px"
|
|
||||||
newButton.style.lineHeight = "34px";
|
|
||||||
newButton.style.align = "center"; //文本居中
|
|
||||||
newButton.style.color = "white"; //按钮文字颜色
|
|
||||||
newButton.style.background = "#1f4bd9"; //按钮底色
|
|
||||||
newButton.style.border = "1px solid #1f4bd9"; //边框属性
|
|
||||||
newButton.style.borderRadius = "3px"; //按钮四个角弧度
|
|
||||||
newButton.style.marginLeft = '10px';
|
|
||||||
newButton.style.fontSize = '12px';
|
|
||||||
newButton.style.padding = '0 10px';
|
|
||||||
newButton.className = "byted-btn byted-btn-size-md byted-btn-type-primary byted-btn-shape-angle byted-can-input-grouped"
|
|
||||||
newButton.style.marginLeft = "10px"
|
|
||||||
newButton.addEventListener("click", handleButtonClick) //监听按钮点击事件
|
|
||||||
|
|
||||||
function appendDoc() {
|
|
||||||
setTimeout(() => {
|
|
||||||
var a = document.querySelector(".rowFlexBox-GWvhwN")
|
|
||||||
if (a) {
|
|
||||||
a.append(newButton)
|
|
||||||
}
|
|
||||||
appendDoc()
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
appendDoc()
|
|
||||||
|
|
||||||
const appID = "1128" //抖音
|
|
||||||
const neededFeatureNames = ['8大消费群体', '预测年龄段', '预测性别', '城市级别'] //需要的画像维度
|
|
||||||
|
|
||||||
// 在脚本的顶部定义一个全局变量
|
|
||||||
var selectedCrowds = {
|
|
||||||
crowdIdList: [],
|
|
||||||
crowdNameList: []
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getID() {
|
|
||||||
//从url中获取aadvid
|
|
||||||
const url = window.location.href
|
|
||||||
const aadvid = url.split('aadvid=')[1]
|
|
||||||
|
|
||||||
// 选择所有选中的复选框所在的行
|
|
||||||
const checkedRows = document.querySelectorAll('.yuntu_analysis-Table-Body .yuntu_analysis-Table-Row .yuntu_analysis-checkbox-checked')
|
|
||||||
console.log(checkedRows)
|
|
||||||
let crowdNameList = []
|
|
||||||
let crowdIdList = []
|
|
||||||
var invalidCrowds = []
|
|
||||||
|
|
||||||
checkedRows.forEach(row => {
|
|
||||||
// 获取人群ID,人群ID位于操作按钮的 data-log-value 属性中
|
|
||||||
const crowdId = row.closest('tr').querySelector('.operationItem-lHPsZh[data-log-value]').getAttribute('data-log-value')
|
|
||||||
|
|
||||||
// 获取人群名称,人群名称位于带有text-D50wBP类的div中
|
|
||||||
const crowdName = row.closest('tr').querySelector('.text-D50wBP').textContent.trim()
|
|
||||||
|
|
||||||
//获取人群包画像操作按钮
|
|
||||||
const crowdProfileOperate = row.closest('tr').querySelector('.operationItem-lHPsZh[data-log-label]')
|
|
||||||
|
|
||||||
//获取人群包画像状态
|
|
||||||
const crowdProfileStatus = crowdProfileOperate.textContent.trim()
|
|
||||||
|
|
||||||
// 检查画像状态是否满足条件
|
|
||||||
if (crowdProfileStatus === "查看画像" && !crowdProfileOperate.classList.contains('disabled-I30WTJ')) { //如果是计算中的人群包,class中会有disabled-I30WTJ
|
|
||||||
crowdIdList.push(crowdId)
|
|
||||||
crowdNameList.push(crowdName)
|
|
||||||
} else {
|
|
||||||
console.log(crowdProfileStatus)
|
|
||||||
|
|
||||||
invalidCrowds.push(crowdName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let crowdList = { crowdIdList, crowdNameList }
|
|
||||||
console.log('crowdList:' + JSON.stringify(crowdList))
|
|
||||||
console.log('invalidCrowds:' + invalidCrowds.join(','))
|
|
||||||
|
|
||||||
return {
|
|
||||||
aadvid,
|
|
||||||
crowdList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchData(url, method, body = null) {
|
|
||||||
const headers = {
|
|
||||||
"content-type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: method,
|
|
||||||
headers: headers
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body) {
|
|
||||||
options.body = JSON.stringify(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, options)
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`)
|
|
||||||
} else {
|
|
||||||
const json = await response.json()
|
|
||||||
return json
|
|
||||||
//只返回data以下的部分
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function fetchProfile(aadvid, crowdId, crowdName) {
|
|
||||||
const url = "https://yuntu.oceanengine.com/yuntu_ng/api/v1/LabelDistribution?aadvid=" + aadvid + "&audience_id=" + crowdId + "&app=" + appID + "&page=1&page_size=100000&features=&label_all_cnt_filter="
|
|
||||||
const profileJson = await fetchData(url, "GET")
|
|
||||||
const profileArr = profileJson.data.labels
|
|
||||||
const processedArr = profileArr
|
|
||||||
.filter(item => neededFeatureNames.includes(item.feature_name))
|
|
||||||
.sort((a, b) => neededFeatureNames.indexOf(a.feature_name) - neededFeatureNames.indexOf(b.feature_name))
|
|
||||||
.map(item => {
|
|
||||||
let result = {
|
|
||||||
feature_name: item.feature_name,
|
|
||||||
label_name: item.label_name
|
|
||||||
}
|
|
||||||
result[crowdName] = String(item.rate) // todo: crowdId 改成crowdName
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
return processedArr
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeArr(arr) {
|
|
||||||
let mergedArray = []
|
|
||||||
let map = new Map()
|
|
||||||
|
|
||||||
arr.forEach(subArr => {
|
|
||||||
subArr.forEach(item => {
|
|
||||||
let key = item['feature_name'] + '-' + item['label_name']
|
|
||||||
let existingItem = map.get(key)
|
|
||||||
if (existingItem) {
|
|
||||||
existingItem = { ...existingItem, ...item }
|
|
||||||
} else {
|
|
||||||
existingItem = { ...item }
|
|
||||||
}
|
|
||||||
map.set(key, existingItem)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
map.forEach(value => {
|
|
||||||
mergedArray.push(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
return mergedArray
|
|
||||||
}
|
|
||||||
|
|
||||||
class chainFunction {
|
|
||||||
async msg() {
|
|
||||||
const Toast = Swal.mixin({
|
|
||||||
toast: true,
|
|
||||||
position: "top-end",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 3000,
|
|
||||||
})
|
|
||||||
Toast.fire({
|
|
||||||
icon: "success",
|
|
||||||
title: "任务开始"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async getProfile() {
|
|
||||||
let profileArr = []
|
|
||||||
for (let i = 0; i < this.crowdIdList.length; i++) {
|
|
||||||
let neededProfile = await fetchProfile(this.aadvid, this.crowdIdList[i], this.crowdNameList[i])
|
|
||||||
console.log(neededProfile) //所需要的画像维度
|
|
||||||
profileArr.push(neededProfile)
|
|
||||||
}
|
|
||||||
console.log(profileArr)
|
|
||||||
this.profileArr = profileArr
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
async getMergedProfile() {
|
|
||||||
let mergedProfile = mergeArr(this.profileArr)
|
|
||||||
this.mergedProfile = mergedProfile
|
|
||||||
console.log(mergedProfile) //合并后的所有画像
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadCsv() {
|
|
||||||
let csv = '';
|
|
||||||
let keys = ['feature_name', 'label_name', ...Object.keys(this.mergedProfile[0]).filter(key => key !== 'feature_name' && key !== 'label_name')]
|
|
||||||
csv += keys.join(',') + '\n'
|
|
||||||
for (let i = 0; i < this.mergedProfile.length; i++) {
|
|
||||||
let values = keys.map(key => this.mergedProfile[i][key])
|
|
||||||
csv += values.join(',') + '\n'
|
|
||||||
}
|
|
||||||
var hiddenElement = document.createElement('a')
|
|
||||||
var fileName = this.crowdNameList.map(name => name.length > 5 ? name.slice(0, 5) : name).join('+')
|
|
||||||
hiddenElement.href = 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURI(csv)
|
|
||||||
hiddenElement.target = '_blank'
|
|
||||||
hiddenElement.download = fileName.substring(0, 30) + ".csv" //只保留30个字符 防止太长Excel打不开
|
|
||||||
hiddenElement.click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 使用链式函数
|
|
||||||
async function handleButtonClick() {
|
|
||||||
try {
|
|
||||||
let ID = getID()
|
|
||||||
// 对当前页面选中的人群包进行去重处理
|
|
||||||
ID.crowdList.crowdIdList.forEach((crowdId, index) => {
|
|
||||||
if (!selectedCrowds.crowdIdList.includes(crowdId)) {
|
|
||||||
selectedCrowds.crowdIdList.push(crowdId)
|
|
||||||
selectedCrowds.crowdNameList.push(ID.crowdList.crowdNameList[index])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
//处理不可用的人群包
|
|
||||||
/*
|
|
||||||
if (ID.invalidCrowds.length > 0) {
|
|
||||||
const Toast = Swal.mixin({
|
|
||||||
toast: true,
|
|
||||||
position: "top-end",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 3000,
|
|
||||||
})
|
|
||||||
Toast.fire({
|
|
||||||
title: "已自动剔除没有画像的人群包: " + ID.invalidCrowds.join(', '),
|
|
||||||
icon: "warning"
|
|
||||||
})
|
|
||||||
ID.invalidCrowds = []
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// 弹出 swal 对话框
|
|
||||||
const { value } = await Swal.fire({
|
|
||||||
title: '选择操作',
|
|
||||||
html: selectedCrowds.crowdNameList.join('<br>'),
|
|
||||||
icon: 'question',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: '下载当前的人群',
|
|
||||||
cancelButtonText: '继续添加'
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
// 如果用户选择直接下载
|
|
||||||
let chain = new chainFunction();
|
|
||||||
await chain.msg();
|
|
||||||
chain.aadvid = ID.aadvid;
|
|
||||||
chain.crowdIdList = selectedCrowds.crowdIdList;
|
|
||||||
chain.crowdNameList = selectedCrowds.crowdNameList;
|
|
||||||
await chain.getProfile();
|
|
||||||
await chain.getMergedProfile();
|
|
||||||
await chain.downloadCsv();
|
|
||||||
// 下载后清空全局变量
|
|
||||||
selectedCrowds.crowdIdList = [];
|
|
||||||
selectedCrowds.crowdNameList = [];
|
|
||||||
} else {
|
|
||||||
// 如果用户选择继续添加人群包,什么也不做,允许用户切换页面继续选择
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})()
|
|
||||||
@ -0,0 +1,803 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name 批量下载人群画像
|
||||||
|
// @namespace https://bbs.tampermonkey.net.cn/
|
||||||
|
// @version 1.0.0
|
||||||
|
// @description 批量下载巨量云图人群画像
|
||||||
|
// @author 汪喜
|
||||||
|
// @match https://yuntu.oceanengine.com/yuntu_ng/analysis/audience/list?*
|
||||||
|
// @match https://yuntu.oceanengine.com/yuntu_brand/analysis/audience/list?*
|
||||||
|
// @match https://yuntu.oceanengine.com/yuntu_brand/ecom/analysis/audience/list?*
|
||||||
|
// @icon https://www.google.com/s2/favicons?domain=oceanengine.com
|
||||||
|
// @require https://cdn.staticfile.net/sweetalert2/11.10.0/sweetalert2.all.min.js
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
var newButton = document.createElement("button")
|
||||||
|
newButton.innerHTML = "下载画像"
|
||||||
|
newButton.type = "button"
|
||||||
|
newButton.style.height = "34px"
|
||||||
|
newButton.style.lineHeight = "34px";
|
||||||
|
newButton.style.align = "center"; //文本居中
|
||||||
|
newButton.style.color = "white"; //按钮文字颜色
|
||||||
|
newButton.style.background = "#a52615"; //按钮底色
|
||||||
|
newButton.style.border = "1px solid #a52615"; //边框属性
|
||||||
|
newButton.style.borderRadius = "3px"; //按钮四个角弧度
|
||||||
|
newButton.style.marginLeft = '10px';
|
||||||
|
newButton.style.fontSize = '12px';
|
||||||
|
newButton.style.padding = '0 10px';
|
||||||
|
newButton.className = "byted-btn byted-btn-size-md byted-btn-type-primary byted-btn-shape-angle byted-can-input-grouped"
|
||||||
|
newButton.style.marginLeft = "10px"
|
||||||
|
newButton.addEventListener("click", handleButtonClick) //监听按钮点击事件
|
||||||
|
|
||||||
|
function appendDoc() {
|
||||||
|
setTimeout(() => {
|
||||||
|
const toolbar = document.querySelector(".rowFlexBox-oqJGZk, .rowFlexBox-GWvhwN")
|
||||||
|
if (toolbar && newButton.parentElement !== toolbar) {
|
||||||
|
toolbar.append(newButton)
|
||||||
|
}
|
||||||
|
appendDoc()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
appendDoc()
|
||||||
|
|
||||||
|
const portraitFeatureOptions = [
|
||||||
|
{
|
||||||
|
key: 'yuntu_city_level',
|
||||||
|
label: '城市级别',
|
||||||
|
requestKeys: ['yuntu_city_level'],
|
||||||
|
responseKeys: ['yuntu_city_level'],
|
||||||
|
responseZh: ['城市级别']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'gender',
|
||||||
|
label: '预测性别',
|
||||||
|
requestKeys: ['gender'],
|
||||||
|
responseKeys: ['gender'],
|
||||||
|
responseZh: ['预测性别']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'new_age_v2',
|
||||||
|
label: '预测年龄段',
|
||||||
|
requestKeys: ['new_age_v2'],
|
||||||
|
responseKeys: ['new_age_v2'],
|
||||||
|
responseZh: ['预测年龄段']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ecom_big8_audience',
|
||||||
|
label: '八大消费群体',
|
||||||
|
requestKeys: ['ecom_big8_audience'],
|
||||||
|
responseKeys: ['ecom_big8_audience'],
|
||||||
|
responseZh: ['八大消费群体', '8大消费群体']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'consuming_capacity',
|
||||||
|
label: '预测消费能力',
|
||||||
|
requestKeys: ['consuming_capacity'],
|
||||||
|
responseKeys: ['consuming_capacity'],
|
||||||
|
responseZh: ['预测消费能力']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'life_stage',
|
||||||
|
label: '预测人生阶段',
|
||||||
|
requestKeys: ['life_stage'],
|
||||||
|
responseKeys: ['life_stage'],
|
||||||
|
responseZh: ['预测人生阶段']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'city',
|
||||||
|
label: '城市',
|
||||||
|
requestKeys: ['city'],
|
||||||
|
responseKeys: ['city'],
|
||||||
|
responseZh: ['城市']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'province',
|
||||||
|
label: '省份',
|
||||||
|
requestKeys: ['province'],
|
||||||
|
responseKeys: ['province'],
|
||||||
|
responseZh: ['省份']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phone_price_preference',
|
||||||
|
label: '手机价格',
|
||||||
|
requestKeys: ['phone_price_preference'],
|
||||||
|
responseKeys: ['phone_price_preference'],
|
||||||
|
responseZh: ['手机价格']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'douyin_active_user',
|
||||||
|
label: '抖音活跃用户',
|
||||||
|
requestKeys: ['douyin_active_user'],
|
||||||
|
responseKeys: ['douyin_active_user'],
|
||||||
|
responseZh: ['抖音活跃用户']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const defaultSelectedFeatureKeys = [
|
||||||
|
'yuntu_city_level',
|
||||||
|
'gender',
|
||||||
|
'new_age_v2',
|
||||||
|
'ecom_big8_audience',
|
||||||
|
'life_stage',
|
||||||
|
'consuming_capacity'
|
||||||
|
]
|
||||||
|
const industryVersion = "10005"
|
||||||
|
const pathId = "analysis_audience_portrait"
|
||||||
|
const runtimeContext = {
|
||||||
|
brandContext: null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在脚本的顶部定义一个全局变量
|
||||||
|
var selectedCrowds = {
|
||||||
|
crowdIdList: [],
|
||||||
|
crowdNameList: [],
|
||||||
|
coverNumberList: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getCellText(row, colIndex, selector = null) {
|
||||||
|
const cell = row.querySelector(`td[aria-colindex="${colIndex}"]`)
|
||||||
|
if (!cell) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = selector ? cell.querySelector(selector) : cell
|
||||||
|
return target ? target.textContent.trim() : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCrowdProfileOperate(row) {
|
||||||
|
const operationItems = Array.from(row.querySelectorAll('[data-log-label][data-log-value]'))
|
||||||
|
return operationItems.find(item => {
|
||||||
|
const label = item.getAttribute('data-log-label') || ""
|
||||||
|
const text = item.textContent.trim()
|
||||||
|
return label.includes('画像') || text.includes('画像')
|
||||||
|
}) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCoverNumber(text) {
|
||||||
|
const normalized = String(text || "").replace(/,/g, "").trim()
|
||||||
|
return normalized && normalized !== "-" ? normalized : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function getID() {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const aadvid = url.searchParams.get('aadvid') || ""
|
||||||
|
|
||||||
|
const checkedRows = document.querySelectorAll('.yuntu_analysis-Table-Body .yuntu_analysis-checkbox-checked')
|
||||||
|
console.log(checkedRows)
|
||||||
|
let crowdNameList = []
|
||||||
|
let crowdIdList = []
|
||||||
|
let coverNumberList = []
|
||||||
|
var invalidCrowds = []
|
||||||
|
|
||||||
|
checkedRows.forEach(checkbox => {
|
||||||
|
const row = checkbox.closest('tr')
|
||||||
|
if (!row) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const crowdName = getCellText(row, 2, '.text-jIwxn6') || getCellText(row, 2)
|
||||||
|
const crowdId = getCellText(row, 3) || (getCrowdProfileOperate(row)?.getAttribute('data-log-value') || "")
|
||||||
|
const coverNumber = normalizeCoverNumber(getCellText(row, 4))
|
||||||
|
const crowdProfileOperate = getCrowdProfileOperate(row)
|
||||||
|
const crowdProfileStatus = crowdProfileOperate ? crowdProfileOperate.textContent.trim() : ""
|
||||||
|
const crowdStatusText = getCellText(row, 5, '.txt-KZswxE') || getCellText(row, 5)
|
||||||
|
const isDisabled = crowdProfileOperate ? /disabled/.test(crowdProfileOperate.className) : true
|
||||||
|
|
||||||
|
if (!crowdId || !crowdName || !crowdProfileOperate) {
|
||||||
|
invalidCrowds.push(crowdName || crowdId || '未知人群')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crowdProfileStatus === "查看画像" && !isDisabled && crowdStatusText === "计算完成") {
|
||||||
|
crowdIdList.push(crowdId)
|
||||||
|
crowdNameList.push(crowdName)
|
||||||
|
coverNumberList.push(coverNumber)
|
||||||
|
} else {
|
||||||
|
console.log(`${crowdName} 状态不满足下载条件: ${crowdStatusText} / ${crowdProfileStatus}`)
|
||||||
|
invalidCrowds.push(crowdName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let crowdList = { crowdIdList, crowdNameList, coverNumberList }
|
||||||
|
console.log('crowdList:' + JSON.stringify(crowdList))
|
||||||
|
console.log('invalidCrowds:' + invalidCrowds.join(','))
|
||||||
|
|
||||||
|
return {
|
||||||
|
aadvid,
|
||||||
|
crowdList,
|
||||||
|
invalidCrowds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBrandMetadata(list) {
|
||||||
|
const candidates = []
|
||||||
|
const seen = new Set()
|
||||||
|
|
||||||
|
for (const item of Array.isArray(list) ? list : []) {
|
||||||
|
const industryId = String(item?.industry_id || "")
|
||||||
|
if (industryId.length !== 2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const brandId = String(item?.brand_id || "")
|
||||||
|
const brandName = String(item?.brand_name || "")
|
||||||
|
const industryName = String(item?.industry_name || "")
|
||||||
|
const key = `${brandId}|${industryId}`
|
||||||
|
|
||||||
|
if (!brandId || !industryId || seen.has(key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
seen.add(key)
|
||||||
|
candidates.push({
|
||||||
|
key,
|
||||||
|
brandId,
|
||||||
|
brandName,
|
||||||
|
industryId,
|
||||||
|
industryName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestJson(url, { method = "GET", body = null, headers = {}, referrer = window.location.href } = {}) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body: body ? JSON.stringify(body) : null,
|
||||||
|
credentials: "include",
|
||||||
|
mode: "cors",
|
||||||
|
referrer
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveBrandContext(aadvid) {
|
||||||
|
if (runtimeContext.brandContext?.aadvid === aadvid) {
|
||||||
|
return runtimeContext.brandContext
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await requestJson(
|
||||||
|
`https://yuntu.oceanengine.com/yuntu_ng/api/v1/get_user_info?aadvid=${encodeURIComponent(aadvid)}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
referrer: window.location.href
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const candidates = normalizeBrandMetadata(response?.data?.brandMetadata)
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
throw new Error("未从 get_user_info 中解析到品牌和行业信息")
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = candidates[0]
|
||||||
|
|
||||||
|
runtimeContext.brandContext = {
|
||||||
|
aadvid,
|
||||||
|
brandId: selected.brandId,
|
||||||
|
brandName: selected.brandName,
|
||||||
|
industryId: selected.industryId,
|
||||||
|
industryName: selected.industryName,
|
||||||
|
industryVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtimeContext.brandContext
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortraitReferrer(crowdId, aadvid) {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.pathname = url.pathname.replace(/\/list$/, "/portrait_v2")
|
||||||
|
url.search = ""
|
||||||
|
url.searchParams.set("cid", crowdId)
|
||||||
|
url.searchParams.set("aadvid", aadvid)
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortraitHeaders() {
|
||||||
|
return {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAudienceBcId(aadvid, crowdId, brandContext) {
|
||||||
|
const requestUrl = new URL("https://yuntu.oceanengine.com/yuntu_ng/api/v1/GetAudienceBcId")
|
||||||
|
requestUrl.searchParams.set("aadvid", aadvid)
|
||||||
|
requestUrl.searchParams.set("custom_audience_id", crowdId)
|
||||||
|
requestUrl.searchParams.set("brand_id", brandContext.brandId)
|
||||||
|
requestUrl.searchParams.set("level_1_industy_id", brandContext.industryId)
|
||||||
|
|
||||||
|
const response = await requestJson(requestUrl.toString(), {
|
||||||
|
method: "GET",
|
||||||
|
referrer: getPortraitReferrer(crowdId, aadvid)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("GetAudienceBcId response:", response)
|
||||||
|
|
||||||
|
const bcId = String(response?.data?.bcId || "")
|
||||||
|
|
||||||
|
if (!bcId) {
|
||||||
|
throw new Error(`未获取到人群 ${crowdId} 的 bcID`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bcId
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeatureOptionMap() {
|
||||||
|
return portraitFeatureOptions.reduce((acc, item) => {
|
||||||
|
acc[item.key] = item
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedFeatureOptions(selectedFeatureKeys) {
|
||||||
|
const featureKeySet = new Set(selectedFeatureKeys)
|
||||||
|
return portraitFeatureOptions.filter(item => featureKeySet.has(item.key))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFeatureRequestKeys(selectedFeatureKeys) {
|
||||||
|
const requestKeys = []
|
||||||
|
|
||||||
|
getSelectedFeatureOptions(selectedFeatureKeys).forEach(item => {
|
||||||
|
item.requestKeys.forEach(key => {
|
||||||
|
if (!requestKeys.includes(key)) {
|
||||||
|
requestKeys.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return requestKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFeatureSelectorHtml(selectedFeatureKeys) {
|
||||||
|
const featureKeySet = new Set(selectedFeatureKeys)
|
||||||
|
const checkboxHtml = portraitFeatureOptions.map(item => {
|
||||||
|
const checked = featureKeySet.has(item.key) ? 'checked' : ''
|
||||||
|
return `
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:13px;">
|
||||||
|
<input type="checkbox" class="portrait-feature-checkbox" value="${item.key}" ${checked}>
|
||||||
|
<span>${item.label}</span>
|
||||||
|
</label>
|
||||||
|
`
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="text-align:left;">
|
||||||
|
<div style="margin-bottom:12px;">
|
||||||
|
<div style="font-weight:600;margin-bottom:6px;">已选人群</div>
|
||||||
|
<div style="max-height:120px;overflow:auto;padding:8px 10px;background:#f6f7f9;border-radius:6px;line-height:1.6;">
|
||||||
|
${selectedCrowds.crowdNameList.join('<br>')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:600;margin-bottom:6px;">画像维度</div>
|
||||||
|
<div style="max-height:220px;overflow:auto;padding:8px 10px;background:#f6f7f9;border-radius:6px;">
|
||||||
|
${checkboxHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showDownloadDialog() {
|
||||||
|
const result = await Swal.fire({
|
||||||
|
title: '下载画像',
|
||||||
|
html: renderFeatureSelectorHtml(defaultSelectedFeatureKeys),
|
||||||
|
width: 560,
|
||||||
|
focusConfirm: false,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: '开始下载',
|
||||||
|
cancelButtonText: '继续添加',
|
||||||
|
preConfirm: () => {
|
||||||
|
const selectedFeatureKeys = Array.from(
|
||||||
|
document.querySelectorAll('.portrait-feature-checkbox:checked')
|
||||||
|
).map(item => item.value)
|
||||||
|
|
||||||
|
if (selectedFeatureKeys.length === 0) {
|
||||||
|
Swal.showValidationMessage('至少选择一个画像维度')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedFeatureKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result.isConfirmed) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortraitLabel(item) {
|
||||||
|
return String(item?.nameZh ?? item?.nameEn ?? '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortraitRate(item) {
|
||||||
|
return String(item?.value ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortraitTgi(item) {
|
||||||
|
return String(item?.tgi ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchFeatureOption(dimension, selectedFeatureKeys) {
|
||||||
|
const selectedOptions = getSelectedFeatureOptions(selectedFeatureKeys)
|
||||||
|
return selectedOptions.find(item => {
|
||||||
|
const nameEn = String(dimension?.nameEn || '')
|
||||||
|
const nameZh = String(dimension?.nameZh || '')
|
||||||
|
return item.responseKeys.includes(nameEn) || item.responseZh.includes(nameZh)
|
||||||
|
}) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPortraitRows(profileJson, crowdName, selectedFeatureKeys) {
|
||||||
|
const dimensions = Array.isArray(profileJson?.data?.data) ? profileJson.data.data : []
|
||||||
|
const rows = []
|
||||||
|
|
||||||
|
dimensions.forEach(dimension => {
|
||||||
|
const featureOption = matchFeatureOption(dimension, selectedFeatureKeys)
|
||||||
|
if (!featureOption) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagDetailList = Array.isArray(dimension?.tagDetailList) ? dimension.tagDetailList : []
|
||||||
|
tagDetailList.forEach(item => {
|
||||||
|
const labelName = getPortraitLabel(item)
|
||||||
|
if (!labelName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
feature_name: featureOption.label,
|
||||||
|
label_name: labelName
|
||||||
|
}
|
||||||
|
row[`${crowdName}_占比`] = getPortraitRate(item)
|
||||||
|
row[`${crowdName}_TGI`] = getPortraitTgi(item)
|
||||||
|
rows.push(row)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProfile(aadvid, crowdId, crowdName, coverNumber, brandContext, selectedFeatureKeys, progressTracker = null) {
|
||||||
|
const bcId = await fetchAudienceBcId(aadvid, crowdId, brandContext)
|
||||||
|
progressTracker?.completeRequest("bcID 已获取", crowdName)
|
||||||
|
const requestUrl = new URL("https://yuntu.oceanengine.com/yuntu_ng/api/v1/crowd_portrait/GetPortraitByBCID")
|
||||||
|
requestUrl.searchParams.set("aadvid", aadvid)
|
||||||
|
const requestBody = {
|
||||||
|
bcID: bcId,
|
||||||
|
featureEnNames: buildFeatureRequestKeys(selectedFeatureKeys),
|
||||||
|
appType: 1,
|
||||||
|
coverNumber: coverNumber || "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("GetPortraitByBCID request:", {
|
||||||
|
method: "POST",
|
||||||
|
url: requestUrl.toString(),
|
||||||
|
crowdId,
|
||||||
|
crowdName,
|
||||||
|
body: requestBody
|
||||||
|
})
|
||||||
|
|
||||||
|
progressTracker?.markProgress("请求画像", crowdName)
|
||||||
|
const profileJson = await requestJson(requestUrl.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
headers: getPortraitHeaders(),
|
||||||
|
referrer: getPortraitReferrer(crowdId, aadvid),
|
||||||
|
body: requestBody,
|
||||||
|
})
|
||||||
|
progressTracker?.completeRequest("画像已获取", crowdName)
|
||||||
|
|
||||||
|
const featureOptionMap = getFeatureOptionMap()
|
||||||
|
const processedArr = extractPortraitRows(profileJson, crowdName, selectedFeatureKeys)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const leftIndex = selectedFeatureKeys.indexOf(
|
||||||
|
Object.keys(featureOptionMap).find(key => featureOptionMap[key].label === a.feature_name)
|
||||||
|
)
|
||||||
|
const rightIndex = selectedFeatureKeys.indexOf(
|
||||||
|
Object.keys(featureOptionMap).find(key => featureOptionMap[key].label === b.feature_name)
|
||||||
|
)
|
||||||
|
return leftIndex - rightIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
if (processedArr.length === 0) {
|
||||||
|
throw new Error(`未从新画像接口解析到 ${crowdName} 的画像数据`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedArr
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeArr(arr) {
|
||||||
|
let mergedArray = []
|
||||||
|
let map = new Map()
|
||||||
|
|
||||||
|
arr.forEach(subArr => {
|
||||||
|
subArr.forEach(item => {
|
||||||
|
let key = item['feature_name'] + '-' + item['label_name']
|
||||||
|
let existingItem = map.get(key)
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem = { ...existingItem, ...item }
|
||||||
|
} else {
|
||||||
|
existingItem = { ...item }
|
||||||
|
}
|
||||||
|
map.set(key, existingItem)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
map.forEach(value => {
|
||||||
|
mergedArray.push(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mergedArray
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
return String(text ?? "").replace(/[&<>"']/g, char => {
|
||||||
|
const entityMap = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
}
|
||||||
|
return entityMap[char] || char
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeFileNamePart(text) {
|
||||||
|
return String(text ?? "")
|
||||||
|
.replace(/[\\/:*?"<>|]/g, "_")
|
||||||
|
.replace(/\s+/g, "")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCrowdSampleName(crowdNameList) {
|
||||||
|
const firstName = sanitizeFileNamePart(crowdNameList?.[0] || "人群")
|
||||||
|
return firstName.length > 8 ? `${firstName.slice(0, 8)}等` : firstName
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestampForFileName(date = new Date()) {
|
||||||
|
const pad = value => String(value).padStart(2, "0")
|
||||||
|
return [
|
||||||
|
date.getFullYear(),
|
||||||
|
pad(date.getMonth() + 1),
|
||||||
|
pad(date.getDate()),
|
||||||
|
"_",
|
||||||
|
pad(date.getHours()),
|
||||||
|
pad(date.getMinutes()),
|
||||||
|
pad(date.getSeconds())
|
||||||
|
].join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCsvFileName(brandName, crowdNameList) {
|
||||||
|
const brandPart = sanitizeFileNamePart(brandName || "品牌")
|
||||||
|
const crowdSamplePart = getCrowdSampleName(crowdNameList)
|
||||||
|
const crowdCountPart = `${crowdNameList.length}个`
|
||||||
|
const timestampPart = formatTimestampForFileName()
|
||||||
|
return `${brandPart}_${crowdSamplePart}_${crowdCountPart}_${timestampPart}.csv`
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(icon, title) {
|
||||||
|
const Toast = Swal.mixin({
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Toast.fire({
|
||||||
|
icon,
|
||||||
|
title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class chainFunction {
|
||||||
|
getProgressPercent() {
|
||||||
|
if (!this.totalRequestCount) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round((this.completedRequestCount / this.totalRequestCount) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProgressHtml(statusText, crowdName = "") {
|
||||||
|
const percent = this.getProgressPercent()
|
||||||
|
const remainingCount = Math.max(this.totalRequestCount - this.completedRequestCount, 0)
|
||||||
|
const crowdHtml = crowdName
|
||||||
|
? `<div style="margin-bottom:8px;color:#5f5f5f;">当前人群:${escapeHtml(crowdName)}</div>`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="min-width:320px;text-align:left;">
|
||||||
|
${crowdHtml}
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-bottom:6px;font-size:12px;">
|
||||||
|
<span>${escapeHtml(statusText)}</span>
|
||||||
|
<span>${this.completedRequestCount}/${this.totalRequestCount}</span>
|
||||||
|
</div>
|
||||||
|
<div style="height:8px;background:#f3d7d2;border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${percent}%;background:#a52615;transition:width .2s ease;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-top:6px;font-size:12px;color:#666;">
|
||||||
|
<span>已完成 ${this.completedRequestCount} 次请求</span>
|
||||||
|
<span>剩余 ${remainingCount} 次请求</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
showProgressToast(statusText, crowdName = "") {
|
||||||
|
const toastOptions = {
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
icon: "info",
|
||||||
|
showConfirmButton: false,
|
||||||
|
showCloseButton: false,
|
||||||
|
allowOutsideClick: false,
|
||||||
|
allowEscapeKey: false,
|
||||||
|
title: "下载画像进度",
|
||||||
|
html: this.renderProgressHtml(statusText, crowdName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Swal.isVisible()) {
|
||||||
|
Swal.update(toastOptions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Swal.fire(toastOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async msg() {
|
||||||
|
this.completedRequestCount = 0
|
||||||
|
this.showProgressToast("准备开始")
|
||||||
|
}
|
||||||
|
markProgress(statusText, crowdName = "") {
|
||||||
|
this.showProgressToast(statusText, crowdName)
|
||||||
|
}
|
||||||
|
completeRequest(statusText, crowdName = "") {
|
||||||
|
this.completedRequestCount += 1
|
||||||
|
this.showProgressToast(statusText, crowdName)
|
||||||
|
}
|
||||||
|
async finish() {
|
||||||
|
if (Swal.isVisible()) {
|
||||||
|
Swal.close()
|
||||||
|
}
|
||||||
|
await showToast("success", `下载完成,共 ${this.crowdNameList.length} 个人群`)
|
||||||
|
}
|
||||||
|
async getProfile() {
|
||||||
|
let profileArr = []
|
||||||
|
for (let i = 0; i < this.crowdIdList.length; i++) {
|
||||||
|
const crowdName = this.crowdNameList[i]
|
||||||
|
this.markProgress("请求 bcID", crowdName)
|
||||||
|
let neededProfile = await fetchProfile(
|
||||||
|
this.aadvid,
|
||||||
|
this.crowdIdList[i],
|
||||||
|
crowdName,
|
||||||
|
this.coverNumberList[i],
|
||||||
|
this.brandContext,
|
||||||
|
this.selectedFeatureKeys,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
console.log(neededProfile) //所需要的画像维度
|
||||||
|
profileArr.push(neededProfile)
|
||||||
|
}
|
||||||
|
console.log(profileArr)
|
||||||
|
this.profileArr = profileArr
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
async getMergedProfile() {
|
||||||
|
let mergedProfile = mergeArr(this.profileArr)
|
||||||
|
this.mergedProfile = mergedProfile
|
||||||
|
console.log(mergedProfile) //合并后的所有画像
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadCsv() {
|
||||||
|
if (!this.mergedProfile || this.mergedProfile.length === 0) {
|
||||||
|
throw new Error("没有可下载的画像数据")
|
||||||
|
}
|
||||||
|
let csv = '';
|
||||||
|
let keys = ['feature_name', 'label_name']
|
||||||
|
this.crowdNameList.forEach(name => {
|
||||||
|
keys.push(`${name}_占比`)
|
||||||
|
keys.push(`${name}_TGI`)
|
||||||
|
})
|
||||||
|
csv += keys.join(',') + '\n'
|
||||||
|
for (let i = 0; i < this.mergedProfile.length; i++) {
|
||||||
|
let values = keys.map(key => this.mergedProfile[i][key])
|
||||||
|
csv += values.join(',') + '\n'
|
||||||
|
}
|
||||||
|
var hiddenElement = document.createElement('a')
|
||||||
|
var fileName = buildCsvFileName(this.brandContext?.brandName, this.crowdNameList)
|
||||||
|
hiddenElement.href = 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURI(csv)
|
||||||
|
hiddenElement.target = '_blank'
|
||||||
|
hiddenElement.download = fileName
|
||||||
|
hiddenElement.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 使用链式函数
|
||||||
|
async function handleButtonClick() {
|
||||||
|
let chain = null
|
||||||
|
try {
|
||||||
|
let ID = getID()
|
||||||
|
if (ID.crowdList.crowdIdList.length === 0) {
|
||||||
|
throw new Error("当前没有选中可下载画像的人群包")
|
||||||
|
}
|
||||||
|
// 对当前页面选中的人群包进行去重处理
|
||||||
|
ID.crowdList.crowdIdList.forEach((crowdId, index) => {
|
||||||
|
if (!selectedCrowds.crowdIdList.includes(crowdId)) {
|
||||||
|
selectedCrowds.crowdIdList.push(crowdId)
|
||||||
|
selectedCrowds.crowdNameList.push(ID.crowdList.crowdNameList[index])
|
||||||
|
selectedCrowds.coverNumberList.push(ID.crowdList.coverNumberList[index])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//处理不可用的人群包
|
||||||
|
/*
|
||||||
|
if (ID.invalidCrowds.length > 0) {
|
||||||
|
const Toast = Swal.mixin({
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
})
|
||||||
|
Toast.fire({
|
||||||
|
title: "已自动剔除没有画像的人群包: " + ID.invalidCrowds.join(', '),
|
||||||
|
icon: "warning"
|
||||||
|
})
|
||||||
|
ID.invalidCrowds = []
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const downloadConfig = await showDownloadDialog()
|
||||||
|
|
||||||
|
if (downloadConfig) {
|
||||||
|
// 如果用户选择直接下载
|
||||||
|
chain = new chainFunction();
|
||||||
|
chain.aadvid = ID.aadvid;
|
||||||
|
chain.crowdIdList = selectedCrowds.crowdIdList;
|
||||||
|
chain.crowdNameList = selectedCrowds.crowdNameList;
|
||||||
|
chain.coverNumberList = selectedCrowds.coverNumberList;
|
||||||
|
chain.selectedFeatureKeys = downloadConfig.selectedFeatureKeys;
|
||||||
|
chain.totalRequestCount = 1 + chain.crowdIdList.length * 2;
|
||||||
|
await chain.msg();
|
||||||
|
chain.markProgress("请求品牌信息");
|
||||||
|
chain.brandContext = await resolveBrandContext(ID.aadvid);
|
||||||
|
chain.completeRequest("品牌信息已获取");
|
||||||
|
await chain.getProfile();
|
||||||
|
await chain.getMergedProfile();
|
||||||
|
await chain.downloadCsv();
|
||||||
|
await chain.finish();
|
||||||
|
// 下载后清空全局变量
|
||||||
|
selectedCrowds.crowdIdList = [];
|
||||||
|
selectedCrowds.crowdNameList = [];
|
||||||
|
selectedCrowds.coverNumberList = [];
|
||||||
|
} else {
|
||||||
|
// 如果用户选择继续添加人群包,什么也不做,允许用户切换页面继续选择
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (Swal.isVisible()) {
|
||||||
|
Swal.close()
|
||||||
|
}
|
||||||
|
await showToast("error", error?.message || "下载画像失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})()
|
||||||
Loading…
x
Reference in New Issue
Block a user