// 处于页面配置时,绑定方法 start
if ($isDecorateMode) {
document.addEventListener("diy_header/1", (event) => {
const { field, value } = event.detail;
console.log(field, value)
});
}
// 处于页面配置时,绑定方法 end
$(function () {
let solutionTabsStickPoint = null
const PAGE_TRANSLATE_STORAGE_KEY = 'shenglan.pageTranslate.locale'
const PAGE_TRANSLATE_CACHE_KEY = 'shenglan.pageTranslate.cache.v2'
const PAGE_TRANSLATE_PENDING_CLASS = 'is-translation-pending'
const MAX_TRANSLATE_BATCH_BYTES = 4000
const MAX_TRANSLATE_BATCH_ITEMS = 30
const translateTextEncoder = new TextEncoder()
let translateTimer = null
$("#logout").click(function () {
$request
.post("/api/user/logout")
.then((res) => {
// 跳转页面
location.href = "/";
});
})
// 防止一开始进入页面在底部不显示
setHeaderSticky()
$(window).on('scroll', setHeaderSticky)
$(window).on('resize', function () {
solutionTabsStickPoint = null
setHeaderSticky()
})
function setHeaderSticky(){
let offset = $(window).scrollTop()
if (offset > 0) {
$('.ys-header').addClass('is-sticky')
} else {
$('.ys-header').removeClass('is-sticky')
}
setSolutionShowcaseState(offset)
}
function setSolutionShowcaseState(offset) {
const tabsWrap = $('.solution-showcase__tabs-wrap')
if (!tabsWrap.length) {
$('body').removeClass('solution-showcase-at-top solution-showcase-tabs-stuck')
solutionTabsStickPoint = null
return
}
if (solutionTabsStickPoint === null) {
refreshSolutionTabsStickPoint(tabsWrap)
}
const isAtTop = offset <= 0
const isTabsStuck = !isAtTop && offset >= solutionTabsStickPoint
$('body').toggleClass('solution-showcase-at-top', isAtTop)
$('body').toggleClass('solution-showcase-tabs-stuck', isTabsStuck)
tabsWrap.toggleClass('is-stuck', isTabsStuck)
}
function refreshSolutionTabsStickPoint(tabsWrap) {
const wasStuck = tabsWrap.hasClass('is-stuck')
const body = $('body')
tabsWrap.removeClass('is-stuck')
body.removeClass('solution-showcase-tabs-stuck')
tabsWrap.addClass('is-stuck')
const stickyTabsHeight = tabsWrap.outerHeight() || 64
tabsWrap.removeClass('is-stuck')
const hero = tabsWrap.prev('.solution-showcase__hero')
const heroBottom = hero.length ? hero.offset().top + hero.outerHeight() : tabsWrap.offset().top
solutionTabsStickPoint = Math.max(0, Math.ceil(heroBottom - stickyTabsHeight))
tabsWrap.css('--solution-tabs-height', stickyTabsHeight + 'px')
tabsWrap.toggleClass('is-stuck', wasStuck)
body.toggleClass('solution-showcase-tabs-stuck', wasStuck)
}
// 隐藏 导航栏 active
function hideActiveCard() {
$('.ys-header__nav .nav-item .ys-header__card.active').stop().fadeOut(300)
}
// 显示 导航栏 active
function showActiveCard() {
$('.ys-header__nav .nav-item .ys-header__card.active').stop().fadeIn(300)
}
// mega menu hover interaction
$('.mega-sidebar-item').mouseenter(function() {
let index = $(this).data('index');
$('.mega-sidebar-item').removeClass('active');
$(this).addClass('active');
$(this).closest('.ys-header__mega').find('.mega-panel').removeClass('active');
$(this).closest('.ys-header__mega').find(`.mega-panel[data-index="${index}"]`).addClass('active');
});
$('.close-mega-menu').click(function() {
$(this).closest('.nav-item').trigger('mouseleave');
});
// 导航栏悬停
$('.ys-header__nav .nav-item').hover(function () {
if(!$(this).find('.ys-header__card').hasClass('active'))
hideActiveCard()
$(this).find('.ys-header__card').addClass('show')
if(!$(this).hasClass('ignore'))
$('.ys-header__cover').removeClass('display-none')
}, function () {
if(!$(this).find('.ys-header__card').hasClass('active'))
showActiveCard()
$(this).find('.ys-header__card').removeClass('show')
$('.ys-header__cover').addClass('display-none')
})
// 搜索图标点击:参考 header/2 展开搜索区域
$('.ys-header__search-icon').click(function () {
openSearchCard()
$('.ys-header__search input').trigger('focus')
})
$('.ys-header__search input').focus(function () {
openSearchCard()
})
function containsTarget(selector, target) {
const node = $(selector)[0]
return !!(node && node.contains(target))
}
$(document).on('click', function (e) {
if (
containsTarget(".ys-header .ys-header__search", e.target) ||
containsTarget(".ys-header .ys-header__search-icon", e.target) ||
$(".ys-header__search input").is(":focus") ||
containsTarget(".ys-header .ys-header__menu", e.target) ||
containsTarget(".ys-header .ys-header-menu", e.target) ||
containsTarget(".ys-header .ys-header-search--mobile", e.target)
) return
closeSearchCard();
})
// 搜索
$('.ys-header__search input').keyup(function (e) {
if (e.keyCode === 13) {
search()
}
})
$('.ys-header__search .search-btn').click(function () {
search()
})
// 联系销售:与 rightNav/1 的在线客服保持一致
$('.ys-header__contact-btn').click(function (e) {
e.preventDefault()
// 优先走全局事件,复用 rightNav/1 的统一客服打开逻辑
window.dispatchEvent(new Event('openService'))
// 兜底:当 rightNav 未挂载时,直接触发已知客服按钮
if ($('#aff-im-root .embed-icon-pcIcon5').length > 0) {
$('#aff-im-root .embed-icon-pcIcon5').click()
return
}
if ($('#newBridge .nb-icon-wrap').length > 0) {
$('#newBridge .nb-icon-wrap').click()
}
})
$('.js-page-translate-btn').click(function (e) {
e.preventDefault()
window.__translateFailed = false
localStorage.setItem(PAGE_TRANSLATE_STORAGE_KEY, 'en')
if (!isEnglishPath(location.pathname)) {
location.href = buildEnglishUrl(location.pathname)
return
}
localizeEnglishModeLinks()
translatePageToEnglish($(this))
})
$('.js-page-translate-reset').click(function (e) {
e.preventDefault()
localStorage.removeItem(PAGE_TRANSLATE_STORAGE_KEY)
delete document.documentElement.dataset.translated
syncTranslateLanguageMenu()
location.href = buildChineseUrl(location.pathname)
})
autoTranslatePageIfNeeded()
function autoTranslatePageIfNeeded() {
if (isEnglishModeRequested()) {
localStorage.setItem(PAGE_TRANSLATE_STORAGE_KEY, 'en')
removeTranslateQueryFlag()
}
syncTranslateLanguageMenu()
if (localStorage.getItem(PAGE_TRANSLATE_STORAGE_KEY) !== 'en') {
revealTranslatedPage()
return
}
localizeEnglishModeLinks()
startTranslateDomObserver()
const $button = $('.js-page-translate-btn')
translatePageToEnglish($button, { silent: true })
}
function isEnglishModeRequested() {
return isEnglishPath(location.pathname) || new URLSearchParams(location.search).get('__translate') === 'en'
}
function syncTranslateLanguageMenu() {
if (isPageTranslatedToEnglish()) {
$('.js-page-translate-reset').removeClass('active')
$('.js-page-translate-btn').addClass('active')
$('.ys-header__lang-trigger span').text('EN')
return
}
$('.js-page-translate-reset').addClass('active')
$('.js-page-translate-btn').removeClass('active')
$('.ys-header__lang-trigger span').text('CN')
}
function isPageTranslatedToEnglish() {
return document.documentElement.dataset.translated === 'en' || localStorage.getItem(PAGE_TRANSLATE_STORAGE_KEY) === 'en' || isEnglishPath(location.pathname)
}
function revealTranslatedPage() {
document.documentElement.classList.remove(PAGE_TRANSLATE_PENDING_CLASS)
}
function stripEnglishPrefix(pathname) {
const mountPrefix = getMountPrefix(pathname)
const pathAfterMount = getPathAfterMount(pathname)
if (pathAfterMount === '/en') return mountPrefix || '/'
if (!pathAfterMount.startsWith('/en/')) return pathname || '/'
return (mountPrefix + pathAfterMount.replace(/^\/en(?=\/)/, '')) || '/'
}
function addEnglishPrefix(pathname) {
const mountPrefix = getMountPrefix(pathname)
const pathAfterMount = getPathAfterMount(pathname)
if (pathAfterMount === '/en' || pathAfterMount.startsWith('/en/')) return pathname || '/'
if (pathAfterMount === '/') return mountPrefix + '/en'
return mountPrefix + '/en' + pathAfterMount
}
function isEnglishPath(pathname) {
const pathAfterMount = getPathAfterMount(pathname)
return pathAfterMount === '/en' || pathAfterMount.startsWith('/en/')
}
function getMountPrefix(pathname = location.pathname) {
return pathname === '/shenglan' || pathname.startsWith('/shenglan/') ? '/shenglan' : ''
}
function getPathAfterMount(pathname = location.pathname) {
const mountPrefix = getMountPrefix(pathname)
if (!mountPrefix) return pathname || '/'
if (pathname === mountPrefix) return '/'
return pathname.slice(mountPrefix.length) || '/'
}
function cleanTranslateSearch() {
const url = new URL(location.href)
url.searchParams.delete('__translate')
return url.search
}
function buildEnglishUrl(pathname) {
return addEnglishPrefix(pathname) + cleanTranslateSearch() + location.hash
}
function buildChineseUrl(pathname) {
return stripEnglishPrefix(pathname) + cleanTranslateSearch() + location.hash
}
function removeTranslateQueryFlag() {
if (new URLSearchParams(location.search).get('__translate') !== 'en') return
const url = new URL(location.href)
url.searchParams.delete('__translate')
history.replaceState(null, '', url.pathname + url.search + url.hash)
}
function localizeEnglishModeLinks(root = document) {
if (localStorage.getItem(PAGE_TRANSLATE_STORAGE_KEY) !== 'en') return
const scope = root && root.nodeType === 1 ? root : document
$(scope).find('a[href], form[action]').addBack('a[href], form[action]').each(function () {
const attr = this.tagName === 'FORM' ? 'action' : 'href'
const value = $(this).attr(attr)
const localized = localizeEnglishModeUrl(value)
if (localized && localized !== value) {
$(this).attr(attr, localized)
}
})
}
function localizeEnglishModeUrl(value) {
if (!value || shouldSkipLocalizedUrl(value)) return value
let url
try {
url = new URL(value, location.href)
} catch (e) {
return value
}
if (url.origin !== location.origin) return value
const mountPrefix = getMountPrefix(location.pathname)
const normalizedPathname = normalizePathnameToCurrentMount(url.pathname, mountPrefix)
if (shouldSkipLocalizedPath(normalizedPathname)) return value
if (isEnglishPath(normalizedPathname)) {
return normalizedPathname !== url.pathname ? normalizedPathname + url.search + url.hash : value
}
const localizedPathname = addEnglishPrefix(normalizedPathname)
return localizedPathname + url.search + url.hash
}
function normalizePathnameToCurrentMount(pathname, mountPrefix) {
if (!mountPrefix) return pathname || '/'
if (pathname === mountPrefix || pathname.startsWith(mountPrefix + '/')) return pathname
return mountPrefix + (pathname === '/' ? '' : pathname)
}
function shouldSkipLocalizedUrl(value) {
const trimmed = String(value).trim()
return (
trimmed === '' ||
trimmed.startsWith('#') ||
/^(javascript|mailto|tel):/i.test(trimmed)
)
}
function shouldSkipLocalizedPath(pathname) {
const pathAfterMount = getPathAfterMount(pathname)
if (pathAfterMount === '/en' || pathAfterMount.startsWith('/en/')) return true
if (/^\/(api|admin|asset|resource|library|load)(\/|$)/.test(pathAfterMount)) return true
if (pathAfterMount === '/') return false
const lastSegment = pathAfterMount.split('/').pop() || ''
const hasFileExtension = /\.[a-z0-9]+$/i.test(lastSegment)
return hasFileExtension && !/\.html$/i.test(lastSegment)
}
function translatePageToEnglish($button, options = {}) {
if ($button.data('loading')) {
revealTranslatedPage()
return
}
const records = collectPageTranslateRecords()
const texts = Array.from(new Set(records.map((record) => record.text))).filter(Boolean)
if (!texts.length) {
if (!options.silent) $message.info('当前页面暂无可翻译中文')
revealTranslatedPage()
return
}
$button.data('loading', true).text('English')
translateTextBatches(texts)
.then((translations) => {
applyPageTranslations(records, translations)
document.documentElement.dataset.translated = 'en'
localStorage.setItem(PAGE_TRANSLATE_STORAGE_KEY, 'en')
localizeEnglishModeLinks()
syncTranslateLanguageMenu()
startTranslateDomObserver()
$button.text('English')
revealTranslatedPage()
if (!options.silent) $message.success('翻译完成')
})
.catch((err) => {
window.__translateFailed = true
$button.text('English')
const msg = err?.message || err?.data?.message || '翻译失败'
if (options.silent) {
console.warn(msg)
} else {
$message.error(msg)
}
revealTranslatedPage()
})
.finally(() => {
$button.data('loading', false)
})
}
function collectPageTranslateRecords() {
const records = []
const skipTags = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG', 'CANVAS', 'IFRAME'])
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
const parent = node.parentElement
if (!parent || skipTags.has(parent.tagName)) return NodeFilter.FILTER_REJECT
if ($(parent).closest('[data-no-translate]').length) return NodeFilter.FILTER_REJECT
return hasChinese(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
},
})
let node
while ((node = walker.nextNode())) {
const original = node.nodeValue
const text = normalizeTranslateText(original.trim())
if (!text) continue
records.push({
type: 'text',
node,
text,
leading: original.match(/^\s*/)?.[0] || '',
trailing: original.match(/\s*$/)?.[0] || '',
})
}
$('[title], [alt], [placeholder], [aria-label]').each(function () {
if ($(this).closest('[data-no-translate]').length) return
;['title', 'alt', 'placeholder', 'aria-label'].forEach((attr) => {
const value = $(this).attr(attr)
if (hasChinese(value || '')) {
records.push({ type: 'attr', node: this, attr, text: normalizeTranslateText(value.trim()) })
}
})
})
return records
}
function translateTextBatches(texts) {
const cachedTranslations = readTranslateCache()
const translationsFromCache = {}
const pendingTexts = []
texts.forEach((text) => {
if (isUsableCachedTranslation(text, cachedTranslations[text])) {
translationsFromCache[text] = cachedTranslations[text]
} else {
pendingTexts.push(text)
}
})
const batches = []
let currentBatch = []
let currentBytes = 0
pendingTexts.forEach((text) => {
const textBytes = translateTextBytes(text) + 1
if (
currentBatch.length > 0 &&
(currentBatch.length >= MAX_TRANSLATE_BATCH_ITEMS || currentBytes + textBytes > MAX_TRANSLATE_BATCH_BYTES)
) {
batches.push(currentBatch)
currentBatch = []
currentBytes = 0
}
currentBatch.push(text)
currentBytes += textBytes
})
if (currentBatch.length) {
batches.push(currentBatch)
}
let batchFailed = false
return batches.reduce((promise, batch) => {
return promise.then((translations) => {
if (batchFailed) return translations
return $request.post('/api/translate/text', { texts: batch }).then((res) => {
const batchTranslations = res.data?.translations || {}
Object.assign(translations, batchTranslations)
writeTranslateCache(batchTranslations)
return translations
}).catch((err) => {
batchFailed = true
console.warn(err?.message || err?.data?.message || '翻译批次失败')
return Promise.reject(err)
})
})
}, Promise.resolve(translationsFromCache))
}
function translateTextBytes(text) {
return translateTextEncoder.encode(text || '').length
}
function readTranslateCache() {
try {
return JSON.parse(localStorage.getItem(PAGE_TRANSLATE_CACHE_KEY) || '{}')
} catch (e) {
return {}
}
}
function writeTranslateCache(translations) {
const usableTranslations = filterUsableTranslations(translations)
if (!Object.keys(usableTranslations).length) return
const cache = readTranslateCache()
Object.assign(cache, usableTranslations)
const keys = Object.keys(cache)
if (keys.length > 1200) {
keys.slice(0, keys.length - 1200).forEach((key) => delete cache[key])
}
localStorage.setItem(PAGE_TRANSLATE_CACHE_KEY, JSON.stringify(cache))
}
function filterUsableTranslations(translations) {
const output = {}
Object.keys(translations || {}).forEach((source) => {
const translated = translations[source]
if (isUsableCachedTranslation(source, translated)) {
output[source] = translated
}
})
return output
}
function isUsableCachedTranslation(source, translated) {
return typeof translated === 'string' && translated !== '' && translated !== source && !hasChinese(translated)
}
function applyPageTranslations(records, translations) {
records.forEach((record) => {
const translated = translations[record.text]
if (!translated || translated === record.text) return
if (record.type === 'text') {
record.node.nodeValue = `${record.leading || ''}${translated}${record.trailing || ''}`
return
}
$(record.node).attr(record.attr, translated)
})
}
function hasChinese(text) {
return /[\u4e00-\u9fff]/.test(text || '')
}
function normalizeTranslateText(text) {
return (text || '').replace(/\s+/g, ' ').trim()
}
function startTranslateDomObserver() {
if (window.__pageTranslateObserver) return
window.__pageTranslateObserver = new MutationObserver(() => {
localizeEnglishModeLinks()
scheduleTranslateRemainingChinese()
})
window.__pageTranslateObserver.observe(document.body, {
childList: true,
subtree: true,
characterData: true,
})
}
function scheduleTranslateRemainingChinese() {
if (localStorage.getItem(PAGE_TRANSLATE_STORAGE_KEY) !== 'en') return
if (window.__translateFailed) return
clearTimeout(translateTimer)
translateTimer = setTimeout(() => {
translatePageToEnglish($('.js-page-translate-btn'), { silent: true })
}, 300)
}
// 展开搜索卡片
function openSearchCard() {
$('.ys-header__nav').addClass('display-none')
$('.ys-header__logo').addClass('display-none')
$('.ys-header__right').addClass('open')
}
// 收起搜索卡片
function closeSearchCard() {
$('.ys-header__nav').removeClass('display-none')
$('.ys-header__logo').removeClass('display-none')
$('.ys-header__right').removeClass('open')
$('.ys-header').removeClass('is-show-mobile-search')
}
// 搜索事件
function search() {
let val = $('.ys-header__search input').val()
if (!val) return $message.warning('请输入搜索关键词')
const searchPath = localStorage.getItem(PAGE_TRANSLATE_STORAGE_KEY) === 'en'
? addEnglishPrefix(normalizePathnameToCurrentMount('/search_result.html', getMountPrefix(location.pathname)))
: normalizePathnameToCurrentMount('/search_result.html', getMountPrefix(location.pathname))
window.location.href = searchPath + '?keyword=' + encodeURIComponent(val)
closeSearchCard()
}
// 产品二级菜单切换
$('.ys-header__nav .product-item').hover(function () {
$('.ys-header__nav .product-child').addClass('display-none')
$('.ys-header__nav .product-item').removeClass('active')
$(this).addClass('active')
$(this).next().removeClass('display-none')
})
// 点击遮罩
$('.ys-header__cover').click(function(){
closeMenuFold()
})
// 移动端搜索
$('.ys-header .ys-header-search--mobile .icon-search').click(function(){
$('.ys-header').addClass('is-show-mobile-search')
openSearchCard()
})
// 抽屉
function openMenuFold() {
$('.ys-header .ys-header-menu').addClass('show')
$('.ys-header__cover').removeClass('display-none')
$('body').addClass('popup-parent--hidden')
}
function closeMenuFold() {
$('.ys-header__cover').addClass('display-none')
$('.ys-header .ys-header-menu').removeClass('show')
$('body').removeClass('popup-parent--hidden')
closeAllMenuSecond()
}
$('.ys-header .ys-header__menu .icon-menu').click(openMenuFold)
$('.ys-header .ys-header-menu .icon-close').click(closeMenuFold)
})
// 二级菜单
$('.ys-header-menu').on('click', '.header-menu__more', function() {
const item = $(this)
const flag = item.next().hasClass('active')
$('.header-menu__list').removeClass('active').stop().slideUp()
if(flag) return
item.next().addClass('active').stop().slideDown()
})
$('.ys-header-menu').on('click', '.header-menu__button', function() {
const item = $(this)
closeAllMenuSecond()
item.next().addClass('active')
})
$('.ys-header-menu').on('click', '.header-second-back', function() {
closeAllMenuSecond()
})
function closeAllMenuSecond() {
$('.header-menu__second').removeClass('active')
}
/* 行动 start */
$('.sustain-action__list').on('mouseover', '.sustain-action__item', function() {
const item = $(this)
if(item.hasClass('active')) return
$('.sustain-action__item.active').removeClass('active')
$(this).addClass('active')
})
/* 行动 end */
// 处于页面配置时,绑定方法
if ($isDecorateMode) {
document.addEventListener('diy_footer/1', (event) => {
const { field, value } = event.detail
console.log(field, value)
if (field === 'footer_ad') {
const nodes = $(
value
.map((item) => ``)
.join('')
)
$('.ys-footer__ad').empty()
$('.ys-footer__ad').append(nodes)
} else if (field === 'contact_phone') {
$('.footer_phone').text(value)
} else if (field === 'work_time') {
$('.footer_work_time').text(value)
} else if (field === 'contact_email') {
$('.footer_email').text(value)
} else if (field === 'footer_nav') {
const nodes = $(
value
.map(
(c) => `
`
)
.join('')
)
$('.ys-footer__content .center-part').empty()
$('.ys-footer__content .center-part').append(nodes)
} else if (field === 'right_part') {
const nodes = $(
value
.map((item) => `
`)
.join('')
)
$('.ys-footer__content .right-part').empty()
$('.ys-footer__content .right-part').append(nodes)
} else if (field === 'friendly_links') {
const nodes = $(
value
.map(
(e) => `