// ==UserScript==
// @name BL Data Query Helper
// @namespace http://tampermonkey.net/
// @version 0.1
// @include http*://www.blushmark.com/*
// @description try to take over the world!
// @author You
// @match http://*/*
// @grant GM_addStyle
// ==/UserScript==
const willRequests = []
const onGoingRequests = []
const maxConcurrency = 5
const beginRequest = (func) => {
if (func) {
if (onGoingRequests.length < maxConcurrency) {
onGoingRequests.push(func)
func()
} else {
willRequests.push(func)
}
}
}
const stopRequest = () => {
onGoingRequests.pop()
if (willRequests.length > 0) {
const func = willRequests.shift()
onGoingRequests.push(func)
func()
}
}
// 强依赖网站的参数
// 1.cookie中的 hasLogin, login_token
// 2.商品div中的class goods-item
// 3.商品div中的class goods-item 的上级div 要有 data-goods-id data-style-id参数
const getQueryStringValue = (key) => {
return decodeURIComponent(window.location.search.replace(new RegExp('^(?:.*[&\\?]' + encodeURIComponent(key).replace(/[\.\+\*]/g, '\\$&') + '(?:\\=([^&]*))?)?.*$', 'i'), '$1'))
}
const getHost = () => {
const hostName = window.location.hostname
let host = 'https://www.blushmark.com/prod'
if (hostName.startsWith('ft') || hostName.startsWith('ft-x')) {
host = `https://${hostName}/test`
} else if (hostName.startsWith('p')) {
host = `https://${hostName}/pre`
}
return host
}
const getUrl = (url, params = {}) => {
let host = getHost()
console.log('url====>', url)
console.log('host====>', host)
console.log('params====>', params)
const keys = Object.keys(params)
keys && keys.map((key) => {
url = url + (url.includes('?') ? '&' : '?') + `${key}=${params[key]}`
})
return host + url
}
const GET = (path, params, callback) => {
beginRequest(() => {
const xhr = new XMLHttpRequest()
const url = getUrl(path, params)
console.log('url====>', url)
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
const info = JSON.parse(xhr.responseText)
if (info && info.code == 0) {
callback(info.data)
} else {
callback(undefined)
}
stopRequest()
}
}
xhr.send()
})
}
const fetchData = (goodsId, styleId, callback) => {
const isAllListPage = window.location.pathname.startsWith('/categories/0_0/')
const isClearance = window.location.pathname.startsWith('/clearance')
const isSortByNew = window.location.search.includes('sort_by=new')
const params = {
styleId: styleId,
callback: goodsId,
type: (isAllListPage ? getQueryStringValue('type') : ''),
pageType: (isClearance ? 'clearance' : isSortByNew ? 'just_in' : 'normal')
}
GET('/1.0/plugin/ctr-detail', params, callback)
}
const indexGetData = (ec, el, list_page_path, callback) => {
const params = {
path: list_page_path,
ec: ec,
el: el
}
GET('/1.0/home/plugin-ctr', params, callback)
}
const productGetData = (goods_id, style_id, size_id, callback) => {
const params = {
goods_id: goods_id,
style_id: style_id,
size_id: size_id
}
GET('/1.0/home/getStockTransit', params, callback)
}
const HTTP = {
fetchData,
indexGetData,
productGetData
}
const elementIdentifier = 'chrome-extension-info'
const elementProductIdentifer = 'chrome-extension-info-product'
const MOUSE_VISITED_CLASS_NAME = 'goods-item'
const MOUSE_VISITED_CLASS_NAME_INDEX = 'ctr-index'
const MOUSE_VISITED_CLASS_NAME_PRODUCT = 'ctr-product'
// 强依赖网站的参数
// 1.cookie中的 hasLogin, login_token
// 2.商品div中的class goods-item
// 3.商品div中的class goods-item 的上级div 要有 data-goods-id data-style-id参数
const createRowElement = (dataElement, text) => {
const container = document.createElement('span')
container.className = 'extension-container'
const textElement = document.createTextNode(text)
container.appendChild(textElement)
dataElement.appendChild(container)
return container
}
/**
* 获取containerElement
* @param element
*/
const getContainerElement = (element) => {
let containerElement = null
if (element && element.className.indexOf(MOUSE_VISITED_CLASS_NAME) >= 0) {
// 商品在此获取数据
const findGoodsImageElementFunc = (elements) => {
elements && Array.from(elements).some(e => {
if (e && e.classList && e.classList.value.indexOf('goods-item-pic') >= 0) {
containerElement = e
return true
}
findGoodsImageElementFunc(e.children)
})
}
findGoodsImageElementFunc(element.children)
} else {
// 首页和详情页在此获取数据
containerElement = element
}
return containerElement
}
const showListCover = (containerElement) => {
if (!containerElement || !containerElement.parentElement) {
return
}
// 商品获取方式
const goodsId = containerElement.parentElement.getAttribute('data-goods-id')
const styleId = containerElement.parentElement.getAttribute('data-style-id')
const pageType = containerElement.parentElement.getAttribute('page-type')
// 参数不合法 退出
if (!goodsId || !styleId || !pageType) return
const children = Array.from(containerElement.children)
let isExist = false
children && children.map(e => {
if (e.id === elementIdentifier) {
if (e.dataId === goodsId + '' + styleId) {
isExist = true
} else {
// 存在一个其他商品的数据 移除
e.parentNode.removeChild(e)
}
}
})
// 已经存在了 不再次展示
if (isExist) return
const dataElement = document.createElement('div')
dataElement.id = elementIdentifier
dataElement.dataId = goodsId + '' + styleId
dataElement.style.zIndex = 100
const ctrElement = createRowElement(dataElement, 'CTR: -%')
let justInElement
if (pageType == 'just_in') {
justInElement = createRowElement(dataElement, 'BST_CTR: -')
}
const crElement = createRowElement(dataElement, 'CR: -%')
const ctrCrElement = createRowElement(dataElement, pageType == 'just_in' ? 'BST_CTR*CR: -' : 'CTR*CR: -')
const saleElement = createRowElement(dataElement, 'SAL: -')
const averageElement = createRowElement(dataElement, 'AVE: -')
const clickElement = createRowElement(dataElement, 'CLI: -')
const impressionElement = createRowElement(dataElement, 'IMP: -')
const userElement = createRowElement(dataElement, 'DET: -')
const ostElement = createRowElement(dataElement, 'OST: -')
const abElement = createRowElement(dataElement, '')
abElement.style.display = 'none'
containerElement && containerElement.appendChild(dataElement)
HTTP.fetchData(goodsId, styleId, (data) => {
if (data) {
const { cr, ctr, sales, viewCount, clickCount, showNumber, onSaleTime, ab_test, ctrCr, goodsSales, bestSellerCtr, averageDailySales } = data
ctrElement.textContent = 'CTR: ' + ctr
crElement.textContent = 'CR: ' + cr
saleElement.textContent = 'SAL: ' + sales + '(' + goodsSales + ')'
clickElement.textContent = 'CLI: ' + clickCount
impressionElement.textContent = 'IMP: ' + viewCount
userElement.textContent = 'DET: ' + showNumber
ostElement.textContent = 'OST: ' + onSaleTime
ctrCrElement.textContent = (pageType == 'just_in' ? 'BST_CTR*CR:' : 'CTR*CR: ') + ctrCr
averageElement.textContent = 'AVE: ' + averageDailySales
if (ab_test) {
abElement.textContent = 'A/B Test'
abElement.style.display = 'block'
}
if (pageType == 'just_in') {
if (bestSellerCtr == undefined) {
justInElement.parentElement.removeChild(justInElement)
} else { justInElement.textContent = 'BST_CTR:' + bestSellerCtr }
}
}
})
}
const showHomeCover = (element, containerElement) => {
if (!element || !containerElement) {
return
}
const children = Array.from(containerElement.children)
let isExist = false
children && children.map(e => {
if (e.id === elementIdentifier) {
isExist = true
}
})
// 已经存在了 不再次展示
if (isExist) return
const ec = element.getAttribute('ec')
const el = element.getAttribute('el')
const list_page_path = element.getAttribute('list_page_path')
// 参数不合法
if (!ec || !el || !list_page_path) return
const dataElement = document.createElement('div')
dataElement.id = elementIdentifier
dataElement.style.zIndex = 100
const ctrElement = createRowElement(dataElement, 'CTR: -%')
const clickElement = createRowElement(dataElement, 'Click: -')
const viewElement = createRowElement(dataElement, 'Impression: -')
HTTP.indexGetData(ec, el, list_page_path, (data) => {
if (data) {
const { ctr, click_count, view_count } = data
ctrElement.textContent = 'CTR: ' + (ctr || '0%')
clickElement.textContent = 'Click: ' + (click_count || 0)
viewElement.textContent = 'Impression: ' + (view_count || 0)
}
})
containerElement && containerElement.appendChild(dataElement)
}
const showDetailCover = (element, containerElement) => {
if (!element || !containerElement) {
return
}
const children = Array.from(containerElement.children)
let isExist = false
children && children.map(e => {
if (e.id === elementProductIdentifer && e.style_id === element.getAttribute('selectedColorId')) {
isExist = true
}
if (e.style_id !== element.getAttribute('selectedColorId')) {
if (e.id === elementProductIdentifer) {
e && e.parentNode.removeChild(e)
}
}
})
// 已经存在了 不再次展示
if (isExist) return
const goods_id = element.getAttribute('selectedGoodsId')
const style_id = element.getAttribute('selectedColorId')
const size_id = element.getAttribute('selectedSizeId')
// 参数不合法
if (!goods_id || !style_id || !size_id) return
const dataElement = document.createElement('div')
dataElement.id = elementProductIdentifer
dataElement.style.zIndex = 1
const showElement = createRowElement(dataElement, '-')
dataElement.style_id = style_id
const showElement2 = createRowElement(dataElement, '')
HTTP.productGetData(goods_id, style_id, size_id, (data) => {
if (data) {
if (data.has_virtual) {
const result = data.stockTransit && data.stockTransit.split('[')
showElement.innerHTML = result[0] + '<br>[' + result[1]
} else {
showElement.innerHTML = data.stockTransit
}
if (data.has_virtual) { showElement2.textContent = 'Virtual' }
}
})
containerElement && containerElement.appendChild(dataElement)
}
const isLogin = () => {
return document.cookie && document.cookie.includes('hasLogin=1;')
}
const beginTimer = () => {
console.log('开始脚本====>')
// 每秒检查 一次
setInterval(() => {
// 遍历所有节点,取所有的商品模块
// 判断该商品模块是否已经有
// 如果没有,则加入模块,并请求接口
// 如果已经有了,则啥都不做
if (isLogin()) {
// 如果是首页商品不展示内容
// 列表页面
const elements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME))
if (window.location.pathname != '/' && elements && elements.length > 0) {
elements.map((element) => {
let containerElement = getContainerElement(element)
showListCover(containerElement)
})
}
// 首页
const indexElements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME_INDEX))
if (indexElements && indexElements.length > 0) {
indexElements.map((element) => {
showHomeCover(element, element)
})
}
// 详情页面
const productElements = Array.from(document.getElementsByClassName(MOUSE_VISITED_CLASS_NAME_PRODUCT))
if (productElements && productElements.length > 0) {
productElements.map((element) => {
showDetailCover(element, element)
})
}
}
}, 1000)
}
const main = () => {
beginTimer()
}
main()
GM_addStyle(`
#chrome-extension-info-product{
position: absolute;
top: 0;
bottom:0;
right: 0;
left: 0;
height: 35px;
}
#chrome-extension-info {
position: absolute;
bottom: 0;
right: 0;
left: 0;
min-height: 60px;
background: rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
padding: 10px 0px;
min-width: 150px;
}
#chrome-extension-info .extension-container {
min-width: calc((100% - 84px)/ 2);
display: inline-block;
font-size: 12px;
color: #333;
font-family: 'Montserrat SemiBold';
line-height: 13px;
text-align: left;
margin-left: 20px;
padding-left: 8px;
position: relative;
margin-bottom: 5px;
}
#chrome-extension-info .extension-container::before {
content: '';
position: absolute;
left: -8px;
top: 0px;
width: 10px;
height: 10px;
border: 1px solid #FFFFFF;
background: #F2CE99;
border-radius: 10px;
line-height: 13px;
}
#chrome-extension-info .extension-container:nth-child(2n+1)::before {
background: #F2CE99;
border: 1px solid #FFFFFF;
}
#chrome-extension-info .extension-container:nth-child(2n)::before {
background: #93D1F5;
border: 1px solid #FFFFFF;
}
#chrome-extension-info-product .extension-container{
position: absolute;
left: 0;
width: 100%;
height: 15px;
text-align: center;
}
#chrome-extension-info-product .extension-container:nth-child(1){
bottom:-15px;
color: red;
}
#chrome-extension-info-product .extension-container:nth-child(2){
top:-15px;
color: blue;
}
`)