PageSourceSearch

https://cdn.etf88.com/js/w_index.js?v=20260810_jjgs_tabcache

js etf88.com collected 2026-10-04 11:20:28 UTC 142,847 bytes, 3,614 lines download raw bytes

1/**
2 * 基金网站全局JavaScript文件
3 * 更新时间: 2025-10-29 16:27
4 * 修改内容: 
5 *   1. 添加净值走势图自动切换时间范围时的按钮状态更新功能
6 *   2. 修复CORS跨域问题:移除withCredentials以兼容通配符CORS头
7 */
8// 🔥 API 服务器地址:使用 api.etf88.com 作为 API 服务器
9// 如果设置为空字符串,则使用相对路径(通过Nginx代理到后端)
10// Nginx会代理 /fundcode/ 和 /user/ 路径到 http://127.0.0.1:3003
11var url = 'https://api.etf88.com';
12
13function isLocalHostEnv() {
14    return window.location.hostname === '127.0.0.1' ||
15        window.location.hostname === 'localhost' ||
16        window.location.hostname === '192.168.1.70';
17}
18
19function getUserApiBase() {
20    return isLocalHostEnv() ? '' : url;
21}
22    /**
23     * @param {date} 标准时间格式:Fri Nov 17 2017 09:26:23 GMT+0800 (中国标准时间)
24     * @param {type} 类型
25     *   type == 1 ---> "yyyy-mm-dd hh:MM:ss.fff"
26     *   type == 2 ---> "yyyymmddhhMMss"
27     *   type == '3' ---> "yyyy-mm-dd"
28     *   type == '4' ---> "yyyy/mmdd"
29     *   type == '5' ---> "yyyy年mm月dd日"
30     *   type == '6' ---> "hh:MM"
31     *   type == '' ---> "yyyy-mm-dd hh:MM:ss"
32     * 
33     * 
34     */
35function formatDate(date, type) {
36    var year = date.getFullYear(); //å¹´
37    var month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; //月
38    var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); //日
39    var hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); //时
40    var minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); //分
41    var seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); //秒
42    var milliseconds = date.getMilliseconds() < 10 ? "0" + date.getMilliseconds() : date.getMilliseconds() //毫秒
43    if (type == 1) {
44        return year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + "." + milliseconds;
45    } else if (type == 2) {
46        return year + "" + month + "" + day + "" + hour + "" + minutes + "" + seconds;
47    } else if (type == 3) {
48        return year + "-" + month + "-" + day;
49    } else if (type == 4) {
50        return year + "/" + month + day;
51    } else if (type == 5) {
52        return year + "年" + month + "月" + day + "日";
53    } else if (type == 6) {
54        return hour + ":" + minutes
55    } else {
56        return year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds;
57    }
58}
59
60function surelogin() {
61    var userApiBase = getUserApiBase();
62    $.ajax({
63        type: "get",
64        url: userApiBase + '/user/islogin?uid=' + getCookie('uid') + '&t=' + Date.parse(new Date()),
65        dataType: 'json',
66        xhrFields: {
67            withCredentials: true
68        },
69        timeout: 120000,
70    }).done(function(result) {
71        if (!result || result.successcode == '0') {
72            resetHomepageLoginState();
73        } else {
74            if (result.uid) {
75                currentLoginUid = String(result.uid);
76                setCookie('uid', currentLoginUid);
77            }
78            if (result.username) {
79                setCookie('username', result.username);
80                syncHomepageLoginDom(result.username);
81            }
82        }
83    }).fail(function(xhr, status, error) {
84        console.error('登录检查失败:', status, error);
85    })
86}
87
88function syncHomepageLoginDom(username) {
89    var displayName = username || getCookie('username') || '';
90    $(".etf-login").first().hide();
91    $(".etf-login").last().show().find(".username").text(displayName);
92    $(".notlogin").hide();
93    $(".islogin").show().find(".username").text(displayName);
94}
95
96function resetHomepageLoginState() {
97    delCookie("username");
98    delCookie("uid");
99    currentLoginUid = '';
100    $(".etf-t-dialog").hide();
101    $(".islogin").hide().find(".username").text('');
102    $(".etf-login").last().hide().find(".username").text('');
103    $(".notlogin").show();
104    $(".etf-login").first().show();
105    $(".discretionaryFund").hide();
106}
107$(function() {
108    if (getCookie('username')) {
109        var username = decodeURIComponent(getCookie('username'))
110        syncHomepageLoginDom(username)
111        $(".discretionaryFund").show()
112            // 检查是否登录
113        surelogin()
114        var islogintimer = ""
115            // 每5分钟
116        islogintimer = setInterval(function() {
117            surelogin()
118        }, 300000);
119    }
120})
121
122var fundcodeLocalCache = null;
123var fundcodeLocalCacheReq = null;
124
125function getSearchFundTypeByPath() {
126    var path = window.location.pathname || '';
127    if (path.indexOf('/qsjhlc/') !== -1) {
128        return 'Q';
129    }
130    if (path.indexOf('/smjj/') !== -1 || path.indexOf('/datacenter/smjj/') !== -1) {
131        return 'S';
132    }
133    return 'F';
134}
135
136function loadFundcodeLocalJson() {
137    if (fundcodeLocalCache) {
138        return Promise.resolve(fundcodeLocalCache);
139    }
140    if (fundcodeLocalCacheReq) {
141        return fundcodeLocalCacheReq;
142    }
143    fundcodeLocalCacheReq = $.ajax({
144        type: 'get',
145        url: 'https://cdn.etf88.com/js/fundcode.json?t=' + Date.now(),
146        dataType: 'json'
147    }).then(function(data) {
148        fundcodeLocalCache = Array.isArray(data) ? data : [];
149        return fundcodeLocalCache;
150    }).catch(function() {
151        fundcodeLocalCache = [];
152        return fundcodeLocalCache;
153    });
154    return fundcodeLocalCacheReq;
155}
156
157function getFundcodeApiBase() {
158    return isLocalHostEnv() ? '' : url;
159}
160
161function renderSearchFundRows(list, SR_box) {
162    if (!list || list.length === 0) {
163        SR_box.show().find('tbody').html('<tr><td colspan="3" style="text-align:center;color:#666">很抱歉,暂无查询结果</td></tr>');
164        return;
165    }
166    var html = '';
167    $.each(list, function(idx, i) {
168        var href = '/jj/' + i.fundcode;
169        var name = i.f001 || i.fundname || '';
170        var py = (i.f002 || i.fundpy || '').trim();
171        html += '<tr data-href="' + href + '" onmouseover="changebg(this,true)" onmouseout="changebg(this,false)"><td class="code">' + i.fundcode + '</td><td class="jjnm" title="' + name + '">' + name + '</td><td class="jjzw" title="' + py + '">' + py + '</td></tr>';
172    });
173    SR_box.show().find('tbody').html(html);
174    $('.se_re_ac_wrap tbody tr').off('click').on('click', function() {
175        var href = $(this).attr('data-href');
176        if (href) {
177            window.open(href);
178        }
179    });
180}
181
182function searchfundViaApi(keyword, SR_box) {
183    $.ajax({
184        type: 'get',
185        url: getFundcodeApiBase() + '/fundcode/getfundcode2?keyword=' + encodeURIComponent(keyword),
186        dataType: 'json'
187    }).done(function(data) {
188        var ok = data && (data.successcode === 1 || data.successcode === '1');
189        if (ok && data.data && data.data.length > 0) {
190            renderSearchFundRows(data.data.slice(0, 10), SR_box);
191        } else {
192            renderSearchFundRows([], SR_box);
193        }
194    }).fail(function() {
195        renderSearchFundRows([], SR_box);
196    });
197}
198
199function searchfund(keyword) {
200    var SR_box = $('.se_re_ac_wrap');
201    var key = (keyword || '').toUpperCase();
202    var expectType = getSearchFundTypeByPath();
203
204    loadFundcodeLocalJson().then(function(allList) {
205        if (!allList || allList.length === 0) {
206            searchfundViaApi(keyword, SR_box);
207            return;
208        }
209        var list = [];
210        for (var j = 0; j < allList.length; j++) {
211            var item = allList[j] || {};
212            var f003 = (item.f003 || '').toUpperCase();
213            if (f003 !== expectType) {
214                continue;
215            }
216            var code = (item.fundcode || '').toUpperCase();
217            var name = item.f001 || '';
218            var py = (item.f002 || '').toUpperCase();
219            if (name.indexOf(keyword) >= 0 || py.indexOf(key) >= 0 || code.indexOf(key) >= 0) {
220                list.push(item);
221                if (list.length >= 10) {
222                    break;
223                }
224            }
225        }
226
227        if (list.length > 0) {
228            renderSearchFundRows(list, SR_box);
229            return;
230        }
231
232        // 本地 fundcode.json 过大或过期时,回退 Redis 搜索接口
233        searchfundViaApi(keyword, SR_box);
234    }).catch(function() {
235        searchfundViaApi(keyword, SR_box);
236    });
237}
238$(function() {
239    var $searchinput = $('.searchkey,.keycode'),
240        $searchbtn = $('.search-submit'),
241        $layer = $('.se_re_ac_wrap'),
242        searchTimer = null
243    $searchinput.on('focus', function() {
244        $('.se_re_ac_wrap').hide();
245        var key = $(this).val()
246        if (key) {
247            $('.se_re_ac_wrap').show()
248        }
249    }).on('click', function() { //阻止冒泡
250        return false;
251    }).on('input', function() {
252        var key = $(this).val()
253        if (key == '拼音简称或基金代码' || !key) {
254            return
255        }
256        if (searchTimer) clearTimeout(searchTimer)
257        searchTimer = setTimeout(function() {
258            searchfund(key)
259        }, 1500)
260
261    });
262    $searchbtn.on('click', function() {
263        var tbodytrs = $('.se_re_ac_wrap tbody tr')
264        var inputval = $(this).parents(".etf-searchbox").find('.searchkey').val()
265        if (inputval == '拼音简称或基金代码' || !inputval) {
266            alert("请填写要查看的基金")
267            return
268        }
269        if (tbodytrs.eq(0).attr('data-href')) {
270            var href = tbodytrs.eq(0).attr('data-href')
271            window.open(href)
272        } else {
273            alert('搜索失败,请返回重新搜索!')
274        }
275    })
276    var nowdate = new Date()
277    onjjcpy = $(".jjcpy,.icon_arrow._jjcpy"),
278        jjcpyul = $(".jjcpyul"),
279        onjjtye = $(".jjtye,.icon_arrow._jjtye"),
280        jjtyeul = $(".jjtyeul"),
281        onjjyear = $(".jjyear,.icon_arrow._jjyear"),
282        jjyearul = $(".jjyearul"),
283        nowyear = nowdate.getFullYear()
284    for (var i = 0; i < nowyear - 1998; i++) {
285        var year = nowyear - i
286        $('.jjyearul').append('<li id="' + year + '">' + year + '</li>')
287    }
288    onjjcpy.on('click', function() {
289        var stuts = jjcpyul.css("display")
290        "block" == stuts ? jjcpyul.hide() : jjcpyul.show()
291        return false;
292    })
293    jjcpyul.on("click", "li", function() {
294        var _this = $(this)
295        $(".jjcpy").attr({
296            "name": _this.attr('id'),
297            "value": _this.text()
298        })
299        $(".jjcpy").val(_this.text())
300    });
301    onjjtye.on('click', function() {
302        var stuts = jjtyeul.css("display")
303        "block" == stuts ? jjtyeul.hide() : jjtyeul.show()
304        return false;
305    })
306    jjtyeul.find("li").each(function(i) {
307        var _this = $(this);
308        _this.on('click', function(e) {
309            $(".jjtye").attr({
310                "name": _this.attr('id'),
311                "value": _this.text()
312            })
313            $(".jjtye").val(_this.text())
314        })
315    })
316    onjjyear.on('click', function() {
317        var stuts = jjyearul.css("display")
318        "block" == stuts ? jjyearul.hide() : jjyearul.show()
319        return false;
320    })
321    jjyearul.find("li").each(function(i) {
322        var _this = $(this);
323        _this.on('click', function(e) {
324            $(".jjyear").attr({
325                "name": _this.attr('id'),
326                "value": _this.text()
327            })
328            $(".jjyear").val(_this.text())
329        })
330    })
331    $(document).on('click', function() {
332            $layer.hide(); //搜索基金代码弹出框
333            $(".jjcpyul,.jjtyeul,.jjyearul").hide();
334        })
335        // 
336    $(".etf-login").hover(
337        function() {
338            $(this).find(".title").addClass("onmouse")
339            $(this).find(".etf-t-dialog").show()
340            $(this).find(".login_error_tips").text("")
341            $("#txt_account,#txt_pwd").removeClass("errline")
342        },
343        function() {
344            $(this).find(".title").removeClass("onmouse")
345            $(".islogin .etf-t-dialog").hide();
346        }
347    )
348    $(".loginbox_close").click(function() {
349        $(".notlogin .etf-t-dialog").hide();
350    });
351    $("#txt_account,#txt_pwd").focus(function() {
352        $("#txt_account,#txt_pwd").removeClass("errline")
353        this.placeholder = ""
354        $(".login_error_tips,.cont-item-tip").text("");
355    })
356    $("#txt_account,#txt_pwd").blur(function() {
357        var id = $(this).attr("id")
358        if (id == "txt_account") {
359            this.placeholder = '请输入用户名'
360        } else {
361            this.placeholder = '请输入密码'
362        }
363    });
364    // 退出登录
365    $(".loginout").click(function() {
366        var userApiBase = getUserApiBase();
367        var uid = currentLoginUid || getCookie('uid') || '';
368        $.ajax({
369            type: "get",
370            url: userApiBase + '/user/logout?uid=' + uid,
371            dataType: 'json',
372            xhrFields: {
373                withCredentials: true
374            },
375        }).done(function(result) {
376            if (result.successcode == '1') {} else {
377                // alert(result.msg)
378            }
379            delCookie("username")
380            delCookie("uid")
381            currentLoginUid = ''
382            $(".notlogin").show()
383            $(".notlogin .etf-t-dialog").hide();
384            $(".islogin").hide().find(".username").text('')
385            document.location.reload(); //
386        })
387    });
388    $(".etf-t-dialog .tologin").bind('click', function() {
389        bindEvent("one1")
390    });
391
392
393})
394
395function bindEvent(mode) {
396    if ($.trim($("#txt_account").val()) == "") {
397        $("#txt_account").addClass("errline")
398        $(".login_error_tips,.cont-item-tip._1").text("用户名不能为空");
399        return false;
400    }
401    if ($("#txt_pwd").val() == "") {
402        $("#txt_account").removeClass("errline")
403        $("#txt_pwd").addClass("errline")
404        $(".login_error_tips,.cont-item-tip._2").text("密码不能为空");
405        return false;
406    }
407    var username = $('#txt_account').val()
408    var password = $('#txt_pwd').val()
409    var param = { 'username': username, 'password': password };
410    var userApiBase = getUserApiBase();
411    $.ajax({
412        type: "get",
413        url: userApiBase + '/user/login',
414        dataType: 'json',
415        data: param,
416        xhrFields: {
417            withCredentials: true
418        },
419    }).done(function(result) {
420        if (result.msg == 'success') {
421            // 🔥 调试日志
422            console.log('[登录成功] 返回数据:', result);
423            
424            // 🔥 修复:直接更新 DOM,不刷新页面(参考首页登录逻辑)
425            // 根据 head.html 的结构,有两个 etf-login div:
426            // 1. 第一个包含登录表单(<!-- 登录 -->)
427            // 2. 第二个包含已登录状态(<!-- 已登录 -->)
428            
429            // 隐藏第一个登录表单
430            $(".etf-login").first().hide();
431            
432            // 显示第二个已登录状态,并更新用户名
433            syncHomepageLoginDom(result.username);
434            
435            // 🔥 设置 cookie(修复 domain 问题)
436            if (result.username) {
437                setCookie("username", result.username);
438                console.log('[登录成功] 设置 username cookie:', result.username);
439            } else {
440                console.warn('[登录成功] ⚠️ result.username 为空');
441            }
442            
443            if (result.uid) {
444                setCookie("uid", result.uid);
445                console.log('[登录成功] 设置 uid cookie:', result.uid);
446            } else {
447                console.warn('[登录成功] ⚠️ result.uid 为空,尝试从 result 中获取');
448                // 如果 result.uid 不存在,尝试其他可能的字段名
449                if (result[0] && result[0].uid) {
450                    setCookie("uid", result[0].uid);
451                    console.log('[登录成功] 从 result[0].uid 设置 cookie:', result[0].uid);
452                }
453            }
454            
455            // 关闭登录弹框
456            $(".etf-t-dialog").hide();
457            
458            if (mode == 'one') {
459                // 登录页面登录
460                if (getCookie("referrer")) {
461                    delCookie("referrer")
462                        // location.href = document.referrer;
463                    history.back()
464                    return false
465                }
466                if (getCookie('tomy') == '0') {
467                    delCookie("tomy")
468                    location.href = "https://my.etf88.com";
469                    return false
470                }
471                location.href = '/'
472            } else {
473                // head登录弹框登录 - 🔥 修复:不再刷新页面,只更新登录区域
474                // document.location.reload(); // 注释掉,改为直接更新 DOM
475                setTimeout(function() {
476                    refreshDatacenterSelfBank();
477                }, 100);
478            }
479
480        } else {
481            $(".login_error_tips").text(result.msg);
482            if (mode == 'one') {
483                alert(result.msg)
484            }
485        }
486    })
487
488}
489// $(function () {
490//     // 文章页右侧随动
491//     if ($(".art-container .sidebarR").hasClass("sidebarR")) {
492//         $(".art-container .sidebarR").stick_in_parent()
493//     }
494// })
495function getUrlKey(name) {
496    return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ""])[1].replace(/\+/g, '%20')) || null
497}
498// 正序倒序排序
499function reverse(obj) {
500    var thistable = $(obj).parents("table")
501    var tbodytr = thistable.find("tbody tr");
502    var total = tbodytr.length;
503
504    tbodytr.each(function(i) {
505        thistable.find("tbody").append(tbodytr.eq((total - 1) - i));
506    });
507}
508
509function changebg(obj, selected) {
510    if (selected)
511        obj.style.background = '#f0f5ff';
512    else
513        obj.style.background = '#ffffff';
514}
515
516function newtable(text, domEment) {
517    var btable = $('.table')
518    if (domEment) btable = $(domEment)
519    var theadths = btable.find('thead tr th')
520    btable.find('tbody').html('<tr><td colspan=' + theadths.length + ' style="text-align:center;color:#666">' + text + '</td></tr>')
521}
522
523
524$(function() {
525    if ($(".rowtable").hasClass("rowtable")) {
526        var rowt = $(".rowtable").find("tr[rowspan]:even")
527        rowt.find("td.tdbg").css({ "background": "#f1f1f1" })
528    }
529})
530
531function OnJDPM() {
532    var startime = $("#sdate").val()
533    var endtime = $("#edate").val()
534    var inputs = $(".sxitem1 input[type='checkbox']")
535    var typeids = []
536    inputs.each(function() {
537        if ($(this).is(':checked')) {
538            typeids.push(Number($(this).val()))
539        }
540    })
541    var param = { 'startime': startime, 'endtime': endtime, 'typeids': typeids };
542    $.ajax({
543        type: "post",
544        url: url + '/user/logout',
545        dataType: 'json',
546    }).done(function(result) {})
547}
548
549function jjcompany() {
550    //全部基金公司
551    $.ajax({
552        type: "get",
553        url: url + '/fundcode/fundcompany',
554        dataType: 'json',
555    }).done(function(data) { //Ajax成功
556        if (data.successcode == '1') {
557            var list = data.data
558            $.each(list, function(idx, obj) {
559                $('.jjcpyul').append('<li id="' + obj.uid + '">' + obj.f001 + '</li>')
560            })
561        }
562    })
563}
564
565function jjclassification() {
566    var list = [{ title: '股票型' }, { title: '混合型' }, { title: '债券型' }, { title: 'QDII' }, { title: 'FOF' }, { title: '保本型' }, ]
567    $.each(list, function(idx, obj) {
568        $('.jjtyeul').append('<li id="' + obj.title + '">' + obj.title + '</li>')
569    })
570}
571
572function setCookie(sName, sValue) {
573    var sCookie = sName + "=" + encodeURIComponent(sValue);
574    sCookie += ";path=/";
575    
576    // 🔥 根据当前域名动态设置 domain
577    var hostname = window.location.hostname;
578    if (hostname && !hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) {
579        // 如果是域名(不是 IP 地址),设置 domain
580        if (hostname.includes('etf88.com')) {
581            sCookie += ";domain=.etf88.com";
582        }
583        // 其他域名(如 localhost)不设置 domain,让浏览器使用默认值
584    }
585    // IP 地址(如 192.168.1.70)不设置 domain,让浏览器使用默认值
586    
587    sCookie += ";expires=" + new Date(new Date().getTime() + 10 * 3600000);
588    document.cookie = sCookie;
589    
590    // 🔥 调试日志:检查 cookie 是否设置成功
591    console.log('[setCookie] 设置 cookie:', sName, '=', sValue, 'hostname:', hostname, 'domain:', hostname && !hostname.match(/^\d+\.\d+\.\d+\.\d+$/) && hostname.includes('etf88.com') ? '.etf88.com' : '未设置');
592}
593
594function getCookie(cname) {
595    var name = cname + "=";
596    var ca = document.cookie.split(';');
597    for (var i = 0; i < ca.length; i++) {
598        var c = ca[i].trim();
599        if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
600    }
601    return "";
602}
603
604function delCookie(name) //删除cookie
605{
606    var exp = new Date();
607    exp.setTime(exp.getTime() - 1);
608    // 同时清理当前域名/IP cookie 与 etf88.com 域名 cookie
609    document.cookie = name + "=;path=/;expires=" + exp.toUTCString();
610    document.cookie = name + "=;path=/;domain=.etf88.com;expires=" + exp.toUTCString();
611    document.cookie = name + "=;path=/;domain=etf88.com;expires=" + exp.toUTCString();
612}
613/**
614 * 存储localStorage
615 */
616function setStore(name, content) {
617    if (!name) return;
618    if (typeof content !== 'string') {
619        content = JSON.stringify(content);
620    }
621    window.localStorage.setItem(name, content);
622}
623
624/**
625 * 获取localStorage
626 */
627function getStore(name) {
628    if (!name) return;
629    var value = window.localStorage.getItem(name);
630    if (value !== null) {
631        try {
632            value = JSON.parse(value);
633        } catch (e) {
634            value = value;
635        }
636    }
637    return value;
638}
639/**
640 * 删除localStorage
641 */
642function removeStore(name) {
643    if (!name) return;
644    window.localStorage.removeItem(name);
645}
646$(function() {
647    $(".table thead .clickable").each(function() {
648        $(this).click(function() {
649            var isort = 1
650            if ($(this).hasClass("sorted-desc") || $(this).hasClass("sorted-asc")) {
651                if ($(this).is('.sorted-desc')) {
652                    isort = 1;
653                }
654                if ($(this).is('.sorted-asc')) {
655                    isort = -1;
656                }
657            } else {
658                isort = -isort
659            }
660            $(".table thead .clickable").removeClass('sorted-asc').removeClass('sorted-desc')
661            if (isort == 1) {
662                $(this).addClass('sorted-asc');
663            } else {
664                $(this).addClass('sorted-desc');
665            }
666            getlist({ isort: isort, sortkey: $(this).attr("paramname") })
667        })
668    })
669})
670
671function startsort(obj, list) {
672    if (obj && obj.sortkey) {
673        list.sort(function(a, b) {
674            var a1 = String(a[obj.sortkey]) == "" ? "1970:01:01" : String(a[obj.sortkey]).indexOf("%") != -1 || String(a[obj.sortkey]).indexOf("亿") != -1 || String(a[obj.sortkey]).indexOf("万") != -1 ? parseFloat(a[obj.sortkey]) : !isNaN(a[obj.sortkey]) ? Number(a[obj.sortkey]) : !isNaN(Date.parse(a[obj.sortkey])) ? a[obj.sortkey] : -1000,
675                b1 = String(b[obj.sortkey]) == "" ? "1970:01:01" : String(b[obj.sortkey]).indexOf("%") != -1 || String(b[obj.sortkey]).indexOf("亿") != -1 || String(b[obj.sortkey]).indexOf("万") != -1 ? parseFloat(b[obj.sortkey]) : !isNaN(b[obj.sortkey]) ? Number(b[obj.sortkey]) : !isNaN(Date.parse(b[obj.sortkey])) ? b[obj.sortkey] : -1000
676            if (a1 < b1) return -obj.isort;
677            if (a1 > b1) return obj.isort; //由小到大
678            return 0;
679        })
680    }
681}
682
683//基金详情拼接链接
684function fundhref(code) {
685    return "/jj/" + code + "/"
686}
687//基金公告文章链接
688function bulletinhref(obj) {
689    return '/jjb/' + dateSlash(obj.pdate) + '/' + obj.jid + '.html'
690}
691
692function dateSlash(date) {
693    return formatDate(new Date(date.replace(/-/g, '/')), 4)
694}
695
696function isjump(classname) {
697    var hasA = $(classname).has("a").length > 0 ? false : true
698    return hasA
699}
700// 纯 JavaScript 实现:.public-tab ul li 通用标签页切换
701(function() {
702    var publicTab = document.querySelector(".public-tab");
703    if (!publicTab) return;
704    
705    var tabButtons = publicTab.querySelectorAll("ul li");
706    var hasA = Array.from(tabButtons).some(function(btn) {
707        return btn.querySelector("a") !== null;
708    });
709    
710    // 只有当标签按钮内没有 <a> 标签时才启用切换功能
711    if (!hasA) {
712        tabButtons.forEach(function(button, index) {
713            button.addEventListener('click', function() {
714                // 更新按钮状态
715                tabButtons.forEach(function(btn) {
716                    btn.classList.remove('cur');
717                });
718                this.classList.add('cur');
719                
720                // 显示对应的表格内容,隐藏其他
721                var contentTables = document.querySelectorAll(".tables-content .rthead-table");
722                contentTables.forEach(function(table, idx) {
723                    if (idx === index) {
724                        table.style.display = '';
725                    } else {
726                        table.style.display = 'none';
727                    }
728                });
729            });
730        });
731    }
732})();
733$(function() {
734    function buildLoadHtmlPath(path) {
735        if (!path) return '';
736        if (path.substring(0, 1) !== "/") return path;
737        if (path.indexOf("/fund20/") === 0 || path.indexOf("?") >= 0 || /\.html(?:$|\?)/.test(path)) {
738            return path;
739        }
740        return path + '.html';
741    }
742
743    // 首页 typelist / 基金公司等子 tab:内存缓存 HTML,避免每次 .load() 打网络
744    var loadHtmlCache = {};
745
746    function setLoadHtmlContent($node, html) {
747        if (!$node || !$node.length) {
748            return;
749        }
750        $node.html(html);
751        $node.removeClass("loadHtml-loading");
752    }
753
754    function fetchLoadHtml(target, onDone) {
755        if (!target) {
756            return;
757        }
758        if (Object.prototype.hasOwnProperty.call(loadHtmlCache, target)) {
759            if (typeof onDone === "function") {
760                onDone(loadHtmlCache[target]);
761            }
762            return;
763        }
764        $.get(target).done(function(html) {
765            loadHtmlCache[target] = html;
766            if (typeof onDone === "function") {
767                onDone(html);
768            }
769        }).fail(function() {
770            if (typeof onDone === "function") {
771                onDone("");
772            }
773        });
774    }
775
776    function loadBoxHtml($box, path) {
777        var target = buildLoadHtmlPath(path);
778        if (!target) {
779            return;
780        }
781        var $loadHtml = $box.find(".loadHtml");
782        if (Object.prototype.hasOwnProperty.call(loadHtmlCache, target)) {
783            setLoadHtmlContent($loadHtml, loadHtmlCache[target]);
784            return;
785        }
786        $loadHtml.addClass("loadHtml-loading");
787        fetchLoadHtml(target, function(html) {
788            // 仅当仍指向同一 path 时写入,避免快速连点覆盖错 tab
789            var cur = buildLoadHtmlPath($box.find(".typelist-tab li.cur_select").attr("data-id")
790                || $box.find(".public2-title span.on").attr("data-id")
791                || path);
792            if (cur && cur !== target && $box.find(".loadHtml").length) {
793                // 若当前选中已变且缓存里有新目标,不强制刷旧内容
794                if (Object.prototype.hasOwnProperty.call(loadHtmlCache, cur)) {
795                    setLoadHtmlContent($loadHtml, loadHtmlCache[cur]);
796                    return;
797                }
798            }
799            setLoadHtmlContent($loadHtml, html);
800        });
801    }
802
803    function prefetchTypelistTabs() {
804        var seen = {};
805        $(".index-page-middle .typelist-tab li[data-id]").each(function() {
806            var path = $(this).attr("data-id");
807            var target = buildLoadHtmlPath(path);
808            if (!target || seen[target] || Object.prototype.hasOwnProperty.call(loadHtmlCache, target)) {
809                return;
810            }
811            // 已 SSI 嵌入的当前块:把现有 HTML 记入缓存
812            var $box = $(this).parents(".public2-box");
813            var $loadHtml = $box.find(".loadHtml");
814            var isCur = $(this).hasClass("cur_select");
815            if (isCur && hasInitialLoadHtml($loadHtml)) {
816                loadHtmlCache[target] = $loadHtml.html();
817                seen[target] = 1;
818                return;
819            }
820            seen[target] = 1;
821            fetchLoadHtml(target);
822        });
823    }
824
825    function hasInitialLoadHtml($node) {
826        if (!$node || $node.length === 0) {
827            return false;
828        }
829        return $.trim($node.html()).length > 0;
830    }
831
832    $(".index-page-middle .public2-title span").click(function() {
833        if ($(this).has("a").length == 0) {
834            console.log("ffff")
835            $(this).addClass("on").siblings().removeClass("on")
836            var tabcontent = $(this).parents(".public2-box").find(".tables-content")
837            tabcontent.find(".typelist-box[father_nav_name='" + $(this).text() + "']").show().siblings(".typelist-box").hide()
838
839            var path = $(this).attr("data-id")
840            if (path) {
841                if (path.substring(0, 1) == "/") {
842                    loadBoxHtml($(this).parents(".public2-box"), path)
843                    $(".typelist-box li[data-id='" + path + "']").addClass("cur_select").siblings().removeClass("cur_select")
844                } else {
845                    var hasfathername = $(this).text()
846                    var txt = tabcontent.find(".typelist-box[father_nav_name='" + $(this).text() + "'] li[class='cur_select']").attr("data-id")
847                    var bp = $(this).parents(".public2-box").find("table[father_nav_name='" + hasfathername + "']")
848                    if (bp.length == 1) {
849                        $(this).parents(".public2-box").find("table[father_nav_name='" + hasfathername + "']").show().siblings("table").hide()
850                    } else {
851                        $(this).parents(".public2-box").find("table[table_id='" + txt + "'][father_nav_name='" + hasfathername + "']").show().siblings("table").hide()
852                    }
853
854                }
855            }
856
857        }
858    })
859    $(".index-page-middle .typelist-tab li").click(function() {
860        if ($(this).has("a").length == 0) {
861            $(this).addClass("cur_select").siblings().removeClass("cur_select")
862            var path = $(this).attr("data-id")
863            var moreHref = $(this).attr("data-more")
864            if (moreHref) {
865                $(this).parents(".public2-box").find(".pub2-more2").attr("href", moreHref)
866            }
867            if (path) {
868                if (path.substring(0, 1) == "/") {
869                    loadBoxHtml($(this).parents(".public2-box"), path)
870                } else {
871                    var hasfathername = $(this).parents(".typelist-box").attr("father_nav_name")
872                    $(this).parents(".public2-box").find("table[table_id='" + $(this).attr("data-id") + "'][father_nav_name='" + hasfathername + "']").show().siblings("table").hide()
873                }
874            } else {
875                $(this).parents(".public2-box").find("table[table_id='" + $(this).text() + "']").show().siblings("table").hide()
876            }
877        }
878    })
879    $('.pt-more').on('click', function() {
880        if ($(this).hasClass('active')) {
881            $(this).removeClass('active').prev('.typelist-more-tab').css({
882                'height': '28px'
883            });
884        } else {
885            $(this).addClass('active').prev('.typelist-more-tab').css({
886                'height': 'auto'
887            });
888        }
889        return false;
890    });
891
892    $(".index-page-middle .public2-box").each(function() {
893        var $box = $(this);
894        var $loadHtml = $box.find(".loadHtml");
895        var autoloadPath = $loadHtml.attr("data-autoload-path");
896        if (!autoloadPath) {
897            autoloadPath = $box.find(".public2-title span.on[data-id]").attr("data-id");
898        }
899        if (autoloadPath && !hasInitialLoadHtml($loadHtml)) {
900            loadBoxHtml($box, autoloadPath);
901        } else if (autoloadPath && hasInitialLoadHtml($loadHtml)) {
902            // SSI 已嵌入:写入内存缓存,切回本 tab 无需再请求
903            var t0 = buildLoadHtmlPath(autoloadPath);
904            if (t0) {
905                loadHtmlCache[t0] = $loadHtml.html();
906            }
907        }
908    });
909
910    // 空闲预取其它子 tab(私募/券商等),暖浏览器缓存与 Redis
911    var schedulePrefetch = window.requestIdleCallback
912        ? function(fn) { window.requestIdleCallback(fn, { timeout: 2500 }); }
913        : function(fn) { setTimeout(fn, 800); };
914    schedulePrefetch(prefetchTypelistTabs);
915
916})
917
918function Sendrequest(apiurl, param, success, fail) { //封装ajax的一些常用参数  //data数据可以为空
919    // 🔥 如果 url 为空字符串,使用相对路径(通过Nginx代理)
920    // 否则使用配置的API地址(用于生产环境)
921    let apiUrl = (typeof url !== 'undefined' && url) ? url : '';
922
923    // 本地/内网调试环境:统一走同域代理,避免跨域/CORS导致接口请求被浏览器拦截
924    if (isLocalHostEnv()) {
925        apiUrl = '';
926    }
927
928    // 本地/内网调试环境下,用户接口强制走同域,保证会话一致
929    var normalizedApiurl = apiurl.startsWith('/') ? apiurl : ('/' + apiurl);
930    // runtime 数据由 www/m 站点的 SSR runtime 层承接,api.etf88.com 没有这些路由。
931    if (normalizedApiurl.indexOf('/runtime/') === 0) {
932        apiUrl = '';
933    }
934    if (isLocalHostEnv() && normalizedApiurl.indexOf('/user/') === 0) {
935        apiUrl = '';
936    }
937    
938    // 构建完整的请求URL
939    // 如果 apiurl 已经包含查询参数,直接使用;否则需
939要处理
940    let finalUrl = '';
941    let requestData = {};
942    
943    // 🔥 确保 apiurl 是相对路径(以 / 开头),如果不是则添加
944    if (!apiurl.startsWith('/')) {
945        apiurl = '/' + apiurl;
946    }
947    
948    // 🔥 确保 apiurl 不是完整的 URL(包含 http:// 或 https://)
949    if (apiurl.indexOf('http://') === 0 || apiurl.indexOf('https://') === 0) {
950        // 如果已经是完整 URL,直接使用(但应该避免这种情况)
951        console.warn('⚠️ Sendrequest 收到完整 URL,应该使用相对路径:', apiurl);
952        finalUrl = apiurl;
953    } else {
954        // 如果 apiUrl 为空,使用相对路径(通过Nginx代理)
955        if (!apiUrl || apiUrl === '') {
956            finalUrl = apiurl;
957            // 如果有 param 参数,使用 jQuery 的 param 方法转换为查询字符串
958            if (param && typeof param === 'object' && Object.keys(param).length > 0) {
959                let queryString = $.param(param);
960                finalUrl = finalUrl + (apiurl.indexOf('?') > -1 ? '&' : '?') + queryString;
961            }
962        } else {
963            // 构建完整的请求URL(生产环境)
964            if (apiurl.indexOf('?') > -1) {
965                // apiurl 已包含查询参数,直接拼接基础URL
966                finalUrl = apiUrl + apiurl;
967            } else {
968                // apiurl 不包含查询参数
969                finalUrl = apiUrl + apiurl;
970                
971                // 如果有 param 参数,使用 jQuery 的 param 方法转换为查询字符串
972                if (param && typeof param === 'object' && Object.keys(param).length > 0) {
973                    let queryString = $.param(param);
974                    finalUrl = finalUrl + '?' + queryString;
975                }
976            }
977        }
978    }
979    
980    // 确保 URL 格式正确(jQuery 会自动处理编码,但为了安全我们显式检查)
981    console.log('Sendrequest请求:', finalUrl, '参数:', requestData, '从域名:', window.location.hostname);
982    
983    var needCredentials = normalizedApiurl.indexOf('/user/') === 0;
984
985    $.ajax({
986        type: "get",
987        url: finalUrl,
988        dataType: 'json',
989        data: requestData,  // 通常为空,因为参数已经在 URL 中
990        // 添加缓存控制,避免缓存问题
991        cache: false,
992        // 用户接口需要携带会话Cookie(跨域到 api.etf88.com 时)
993        xhrFields: needCredentials ? { withCredentials: true } : undefined,
994        timeout: 120000,  // 增加超时时间到120秒,与Nginx配置一致
995        error: function(xhr, status, error) {
996            console.error('请求失败:', finalUrl, '状态:', status, '错误:', error, 'HTTP状态码:', xhr.status, '响应文本:', xhr.responseText);
997            if (fail && typeof fail === 'function') {
998                fail(xhr, status, error);
999            }
1000        },
1001        success: function(data) {
1002            if (success && typeof success === 'function') {
1003                success(data);
1004            }
1005        }
1006    });
1007}
1008
1009function deletcompare(fundcode) {
1010    var list = getStore("fundcompare").split(","),
1011        code = ''
1012    $.each(list, function(index, item) {
1013        if (fundcode !== item) {
1014            code += "," + item
1015        }
1016    })
1017    setStore("fundcompare", code.substring(1))
1018    getlist()
1019}
1020
1021function addcompare() {
1022    var fundcode = $(".keycode").val()
1023    if (!fundcode) return
1024    var seresultcode = $(".sxitem .se_re_ac_wrap tbody tr")
1025    if (!seresultcode.is("[fundcode=" + fundcode + "]")) {
1026        alert("请您在搜索的结果中选择一个基金。");
1027        return
1028    }
1029    var code = fundcode
1030    if (getStore("fundcompare")) {
1031        var list = getStore("fundcompare").split(","),
1032            index = $.inArray(fundcode, list)
1033        if (index >= 0) {
1034            alert("该基金已经添加,无需再添加")
1035            return
1036        }
1037        code = getStore("fundcompare") + "," + fundcode
1038    }
1039    setStore("fundcompare", code)
1040    getlist()
1041}
1042
1043// 纯 JavaScript 实现:.fund-d-c-content .public2-title span 切换(基金净值、基金分红、基金拆分)
1044(function() {
1045    var fundContentContainers = document.querySelectorAll(".fund-d-c-content");
1046    
1047    fundContentContainers.forEach(function(container) {
1048        if (container.id === 'rating-v2-section') {
1049            return;
1050        }
1051        var titleSpans = container.querySelectorAll(".public2-title span");
1052        
1053        titleSpans.forEach(function(span, index) {
1054            // 检查是否包含 <a> 标签
1055            if (span.querySelector("a") === null) {
1056                span.addEventListener('click', function() {
1057                    // 更新按钮状态
1058                    titleSpans.forEach(function(s) {
1059                        s.classList.remove('on');
1060                    });
1061                    this.classList.add('on');
1062                    
1063                    // 显示对应的"更多"链接
1064                    var titleText = this.textContent.trim();
1065                    var moreLinks = container.querySelectorAll(".pub2-more2");
1066                    moreLinks.forEach(function(link) {
1067                        if (link.getAttribute("show-title") === titleText) {
1068                            link.style.display = '';
1069                        } else {
1070                            link.style.display = 'none';
1071                        }
1072                    });
1073                    
1074                    // 显示对应的表格,隐藏其他
1075                    var tables = container.querySelectorAll(".hq-wrap table");
1076                    tables.forEach(function(table, idx) {
1077                        if (idx === index) {
1078                            table.style.display = '';
1079                        } else {
1080                            table.style.display = 'none';
1081                        }
1082                    });
1083                });
1084            }
1085        });
1086    });
1087})();
1088$(function() {
1089        var clickicons = $(".clickicon")
1090        clickicons.each(function() {
1091            var _this = $(this)
1092            var log = true
1093            _this.click(function() {
1094                log ? _this.addClass("icon-up").removeClass("icon-down") : _this.addClass("icon-down").removeClass("icon-up")
1095                log = !log
1096                _this.parents("td").find(".chart-5").slideToggle(500)
1097            })
1098        })
1099    })
1100    // 自选基金
1101var zxjjfundId = 1
1102var currentLoginUid = ''
1103var selfFundLoadingState = { lastTime: 0, lastSfid: '', loading: false }
1104getzxjjlist()
1105
1106function refreshDatacenterSelfBank() {
1107    if (window.location.pathname.indexOf('/datacenter/') !== 0) {
1108        return;
1109    }
1110    if (!getLoginDisplayName()) {
1111        return;
1112    }
1113    getzxjjlist();
1114}
1115
1116function syncLoginStateForDatacenterFundLinks(callback) {
1117    var done = function(isLogged) {
1118        if (typeof callback === 'function') callback(!!isLogged);
1119    };
1120
1121    Sendrequest('/user/islogin?t=' + Date.parse(new Date()), null, function success(data) {
1122        var isLogged = false;
1123        if (data && data.successcode == '1') {
1124            isLogged = true;
1125            if (data.uid) {
1126                currentLoginUid = String(data.uid);
1127                setCookie('uid', currentLoginUid);
1128            }
1129            if (data.username) {
1130                setCookie('username', data.username);
1131            }
1132        }
1133        done(isLogged);
1134    }, function() {
1135        done(false);
1136    });
1137}
1138
1139function openLoginDialogWithMessage(message) {
1140    // 复用站内已有顶部登录弹窗
1141    var $notLogin = $('.notlogin').first();
1142    if ($notLogin.length === 0) return;
1143    $notLogin.show();
1144    $notLogin.find('.title').addClass('onmouse');
1145    $notLogin.find('.etf-t-dialog').show();
1146    $notLogin.find('.login_error_tips').text(message || '请登录后再操作');
1147}
1148
1149function promptDatacenterLogin() {
1150    openLoginDialogWithMessage('请登录后查看基金详情');
1151}
1152
1153function initDatacenterFundLinkLoginSync() {
1154    // 已按需求移除 datacenter 基金详情跳转登录拦截
1155    return;
1156}
1157
1158function getLoginDisplayName() {
1159    var name = (getCookie('username') || '').trim();
1160    if (name) return name;
1161    var domName = $('.islogin .username:visible').first().text().trim();
1162    if (domName && domName !== '用户') return domName;
1163    domName = $('.title.username:visible').first().text().trim();
1164    if (domName && domName !== '用户') return domName;
1165    return '';
1166}
1167
1168function resolveLoginUid(done, fail) {
1169    var uid = (getCookie('uid') || '').trim();
1170    if (uid) {
1171        currentLoginUid = uid;
1172        done(uid);
1173        return;
1174    }
1175
1176    Sendrequest('/user/islogin?t=' + Date.parse(new Date()), null, function success(data) {
1177        if (data && data.successcode == '1' && data.uid) {
1178            currentLoginUid = String(data.uid);
1179            setCookie('uid', currentLoginUid);
1180            if (data.username) {
1181                setCookie('username', data.username);
1182            }
1183            done(currentLoginUid);
1184            return;
1185        }
1186        if (fail) fail();
1187    }, function() {
1188        if (fail) fail();
1189    });
1190}
1191
1192function getzxjjlist() {
1193    var haszxjj = $(".discretionaryFund").has("#zxjjlist").length > 0
1194    var isDatacenterPage = window.location.pathname.indexOf('/datacenter/') === 0;
1195    if (haszxjj) {
1196        if (getLoginDisplayName()) {
1197            // datacenter 页面显示自选区,但隐藏左侧标题文案,仅保留下拉
1198            if (isDatacenterPage) {
1199                $(".discretionaryFund").show();
1200                $(".discretionaryFund .pagename").hide();
1201                $(".discretionaryFund .sxcontent").css({
1202                    'float': 'right',
1203                    'text-align': 'right',
1204                    'width': 'auto'
1205                });
1206                $(".discretionaryFund .sxitem").css({
1207                    'float': 'right'
1208                });
1209            } else {
1210                $(".discretionaryFund").show()
1211            }
1212        } else {
1213            $(".discretionaryFund").hide()
1214            return
1215        }
1216
1217        resolveLoginUid(function(uid) {
1218            Sendrequest('/user/getusertemplates?uid=' + uid, null, function success(data) {
1219                if (data.successcode == '1') {
1220                    var list = data.data
1221                    getbklist(list)
1222                }
1223            }, function fail() {})
1224        }, function() {
1225            $(".discretionaryFund").hide()
1226        })
1227    }
1228
1229}
1230
1231function getbklist(data) {
1232    var content = '';
1233    // 如果没有数据,显示默认选项
1234    if (!data || data.length === 0) {
1235        content = "<option value='1' selected>默认</option>";
1236    } else {
1237        for (i = 0; i < data.length; i++) {
1238            var sfid = String(data[i]['sfid'] || '');
1239            var sfname = (data[i]['sfname'] || '').trim();
1240            if (!sfname) {
1241                if (sfid === '1') {
1242                    sfname = '默认';
1243                } else if (sfid === '2') {
1244                    sfname = '货币自选';
1245                } else {
1246                    sfname = '自选' + sfid;
1247                }
1248            }
1249            var selected = data[i]['sfid'] == '1' ? ' selected' : '';
1250            content += "<option value='" + data[i]['sfid'] + "'" + selected + ">" + sfname + "</option>";
1251        }
1252    }
1253    content = "<span class='self-bank-label' style='margin-right:8px;'>自选版块:</span><select class='selef_zxbk' id='bank_value' onchange='GetSelfFund();'>" + content + "</select>";
1254
1255    var isDatacenterPage = window.location.pathname.indexOf('/datacenter/') === 0;
1256
1257    // datacenter 在没有页面构建器时,可能不存在 #self_bank,这里补一个容器
1258    if (isDatacenterPage && $('#self_bank').length === 0) {
1259        var $sxItem = $('.discretionaryFund .sxitem').first();
1260        if ($sxItem.length === 0) {
1261            $('.discretionaryFund').prepend('<div class="sxbox"><div class="sxcontent"><div class="sxitem"></div></div></div>');
1262            $sxItem = $('.discretionaryFund .sxitem').first();
1263        }
1264        $sxItem.html('<span id="self_bank" style="display:inline-block;float:right;"></span>');
1265    }
1266
1267    // datacenter 场景优先渲染到自选区域内的 #self_bank
1268    var $targetSelfBank = $('#datacenter-container .discretionaryFund #self_bank').first();
1269    if ($targetSelfBank.length === 0) {
1270        $targetSelfBank = $('#self_bank').first();
1271    }
1272    $targetSelfBank.html(content);
1273
1274    // 确保默认选中 value='1' 的选项
1275    if ($targetSelfBank.find("option[value='1']").length > 0) {
1276        $targetSelfBank.find("option[value='1']").attr("selected", "selected");
1277    } else if ($targetSelfBank.find("option").length > 0) {
1278        // 如果没有 value='1' 的选项,选中第一个选项
1279        $targetSelfBank.find("option:first").attr("selected", "selected");
1280    }
1281    GetSelfFund()
1282}
1283
1284function GetSelfFund() {
1285    var uid = currentLoginUid || (getCookie('uid') || '').trim();
1286    if (!uid) {
1287        newtable("账号未登录", "#zxjjlist");
1288        return;
1289    }
1290    var sfid = $('#datacenter-container .discretionaryFund #bank_value').val() || $('#self_bank option:selected').val() || '1';
1291
1292    // 去重:页面初始化阶段可能由多个逻辑同时触发,避免重复出现“正在加载中”
1293    var now = Date.now();
1294    if (selfFundLoadingState.loading && selfFundLoadingState.lastSfid === String(sfid)) {
1295        return;
1296    }
1297    if ((now - selfFundLoadingState.lastTime) < 1000 && selfFundLoadingState.lastSfid === String(sfid)) {
1298        return;
1299    }
1300
1301    selfFundLoadingState.loading = true;
1302    selfFundLoadingState.lastTime = now;
1303    selfFundLoadingState.lastSfid = String(sfid);
1304
1305    zxjjfundId = sfid
1306    newtable("正在加载中,请稍等...", "#zxjjlist");
1307    Sendrequest('/user/getusertemplatesdata?uid=' + uid + '&sfid=' + sfid, null, function success(data) {
1308        selfFundLoadingState.loading = false;
1309        if (data.successcode == '1') {
1310            var list = data.data
1311            GetSelfFundResult(list)
1312        } else {
1313            newtable("暂无数据", "#zxjjlist");
1314        }
1315    }, function fail() {
1316        selfFundLoadingState.loading = false;
1317        newtable("请求失败", "#zxjjlist");
1318    })
1319}
1320
1321function GetSelfFundResult(list) {
1322    var tbody = $("#zxjjlist tbody").html('')
1323    var html = ""
1324    var isMoneyPage = window.location.pathname.indexOf('jjdata_hbxjz') !== -1
1325
1326    if (isMoneyPage) {
1327        $('#zxjjlist thead th').filter(function() {
1328            return $(this).text().trim() === '涨幅';
1329        }).remove();
1330    }
1331
1332    $.each(list, function(idx, i) {
1333        let zdcolor = parseFloat(i.zd) > 0 ? "red" : "green";
1334        let zfcolor = parseFloat(i.zf) > 0 ? "red" : "green";
1335        if (!isNaN(i.zf)) i.zf = i.zf + '%';
1336        if (isMoneyPage) {
1337            html += '<tr><td><a href="' + fundhref(i.fundcode) + '" target="_blank">' + i.fundcode + '</a></td><td><a href="' + fundhref(i.fundcode) + '" target="_blank" title="' + i.fundname + '">' + i.fundname + '</a></td><td>' + i.jjlx + '</td><td>' + i.zxrq + '</td><td>' + i.zxjz + '</td><td>' + i.ljjz + '</td><td>' + i.sqjz + '</td><td>' + i.sqlj + '</td><td class="' + zdcolor + '">' + i.zd + '</td><td class="delsefund" width="80"> <a onclick="DelSelfFund(' + "'" + i.fundcode + "'" + ')">删除</a></td></tr>'
1338        } else {
1339            html += '<tr><td><a href="' + fundhref(i.fundcode) + '" target="_blank">' + i.fundcode + '</a></td><td><a href="' + fundhref(i.fundcode) + '" target="_blank" title="' + i.fundname + '">' + i.fundname + '</a></td><td>' + i.jjlx + '</td><td>' + i.zxrq + '</td><td>' + i.zxjz + '</td><td>' + i.ljjz + '</td><td>' + i.sqjz + '</td><td>' + i.sqlj + '</td><td class="' + zdcolor + '">' + i.zd + '</td><td class="' + zfcolor + '">' + i.zf + '</td><td class="delsefund" width="80"> <a onclick="DelSelfFund(' + "'" + i.fundcode + "'" + ')">删除</a></td></tr>'
1340        }
1341    });
1342    tbody.html(html)
1343}
1344
1345function DelSelfFund(fundcode) {
1346    var sfid = $('#self_bank option:selected').val()
1347    Sendrequest('/user/delusertemplatesdata?uid=' + getCookie('uid') + '&sfid=' + sfid + '&fundcode=' + fundcode, null, function success(data) {
1348        if (data.successcode == '1') {
1349            GetSelfFund()
1350        } else {
1351            alert("删除失败")
1352        }
1353    }, function fail() {
1354        alert("请求失败");
1355    })
1356}
1357
1358function iszx(fundcode, type) {
1359    //是否自选
1360    var sfid = type ? type : zxjjfundId
1361    Sendrequest('/user/ishaveusertemplatesdata?uid=' + getCookie('uid') + '&sfid=' + sfid + '&fundcode=' + fundcode, null, function success(data) {
1362        if (data.successcode == '1') {
1363            $(".GJ_ad_zx_btn").text("-取消自选").addClass("on")
1364        } else {}
1365    }, function fail() {})
1366}
1367
1368function delzx(fundcode, type) {
1369    //删除自选
1370    var sfid = type ? type : zxjjfundId
1371    Sendrequest('/user/delusertemplatesdata?uid=' + getCookie('uid') + '&sfid=' + sfid + '&fundcode=' + fundcode, null, function success(data) {
1372        if (data.successcode == '1') {
1373            if ($(".GJ_ad_zx_btn").length > 0) {
1374                $(".GJ_ad_zx_btn").text("+添加自选").removeClass("on")
1375            }
1376        } else {
1377            alert(data.msg)
1378        }
1379    }, function fail() {})
1380}
1381
1382function issetzx(fundcode, type) {
1383    //加入自选
1384    var sfid = type ? type : zxjjfundId
1385    
1386    // 🔥 调试日志
1387    console.log('[issetzx] 开始添加自选,fundcode:', fundcode, 'type:', type, 'sfid:', sfid);
1388    
1389    // 🔥 检查登录状态:优先从 cookie 获取,如果 cookie 中没有,检查页面元素(SSR 已渲染登录状态)
1390    var username = getCookie('username');
1391    var uid = getCookie('uid');
1392    
1393    console.log('[issetzx] Cookie 检查结果: username=', username, 'uid=', uid);
1394    
1395    // 如果 cookie 中没有用户名,检查页面中是否显示用户名(SSR 渲染的登录状态)
1396    if (!username) {
1397        var usernameElement = $(".title.username");
1398        var usernameText = usernameElement.length > 0 ? usernameElement.text().trim() : '';
1399        console.log('[issetzx] Cookie 中没有用户名,检查页面元素: usernameElement.length=', usernameElement.length, 'usernameText=', usernameText);
1400        
1401        if (usernameElement.length > 0 && usernameText !== '' && usernameText !== '用户') {
1402            // 页面中显示用户名,说明已登录(SSR 渲染)
1403            console.log('[issetzx] 页面中显示用户名,说明已登录(SSR 渲染),调用 API 获取 uid');
1404            // 尝试从登录状态 API 获取 uid(API 会检查 session,不传 uid 参数)
1405            Sendrequest('/user/islogin?t=' + Date.parse(new Date()), null, function success(data) {
1406                console.log('[issetzx] islogin API 响应:', data);
1407                if (data && data.successcode == '1' && data.uid) {
1408                    // 已登录,获取 uid
1409                    console.log('[issetzx] ✅ 已登录,获取到 uid:', data.uid);
1410                    executeAddFavorite(fundcode, sfid, data.uid);
1411                } else {
1412                    console.warn('[issetzx] ❌ islogin API 返回未登录或没有 uid');
1413                    openLoginDialogWithMessage('请登录后再添加自选!')
1414                }
1415            }, function fail(error) {
1416                console.error('[issetzx] ❌ islogin API 调用失败:', error);
1417                openLoginDialogWithMessage('请登录后再添加自选!')
1418            });
1419            return;
1420        } else {
1421            // 页面中也没有用户名,说明未登录
1422            console.warn('[issetzx] ❌ Cookie 和页面元素都没有用户名,说明未登录');
1423            openLoginDialogWithMessage('请登录后再添加自选!')
1424            return
1425        }
1426    }
1427    
1428    // 如果 cookie 中有用户名但没有 uid,尝试从 API 获取
1429    if (username && !uid) {
1430        console.log('[issetzx] Cookie 中有用户名但没有 uid,调用 API 获取 uid');
1431        Sendrequest('/user/islogin?t=' + Date.parse(new Date()), null, function success(data) {
1432            console.log('[issetzx] islogin API 响应:', data);
1433            if (data && data.successcode == '1' && data.uid) {
1434                console.log('[issetzx] ✅ 已登录,获取到 uid:', data.uid);
1435                executeAddFavorite(fundcode, sfid, data.uid);
1436            } else {
1437                console.warn('[issetzx] ❌ islogin API 返回未登录或没有 uid');
1438                openLoginDialogWithMessage('请登录后再添加自选!')
1439            }
1440        }, function fail(error) {
1441            console.error('[issetzx] ❌ islogin API 调用失败:', error);
1442            openLoginDialogWithMessage('请登录后再添加自选!')
1443        });
1444        return;
1445    }
1446    
1447    // 执行添加自选
1448    console.log('[issetzx] ✅ Cookie 中有用户名和 uid,直接执行添加自选');
1449    executeAddFavorite(fundcode, sfid, uid);
1450}
1451
1452// 执行添加自选的辅助函数
1453function executeAddFavorite(fundcode, sfid, uid) {
1454    console.log('[executeAddFavorite] 开始执行添加自选,fundcode:', fundcode, 'sfid:', sfid, 'uid:', uid);
1455    
1456    if (!uid) {
1457        console.error('[executeAddFavorite] ❌ uid 为空,无法添加自选');
1458        alert("无法获取用户ID,请刷新页面后重试");
1459        return;
1460    }
1461    
1462    Sendrequest('/user/saveusertemplatesdata?uid=' + uid + '&sfid=' + sfid + '&fundcode=' + fundcode, null, function success(data) {
1463        console.log('[executeAddFavorite] saveusertemplatesdata API 响应:', data);
1464        if (data.successcode == '1') {
1465            console.log('[executeAddFavorite] ✅ 添加自选成功');
1466            getzxjjlist()
1467            if ($(".GJ_ad_zx_btn").length > 0) {
1468                $(".GJ_ad_zx_btn").text("-取消自选").addClass("on")
1469            }
1470            alert("添加成功!")
1471        } else {
1472            console.warn('[executeAddFavorite] ❌ 添加自选失败:', data.msg);
1473            alert(data.msg || '添加失败')
1474        }
1475    }, function fail(error) {
1476        console.error('[executeAddFavorite] ❌ API 调用失败:', error);
1477        alert('请求失败,请稍后再试')
1478    })
1479}
1480
1481function changecolor(value) {
1482    return parseFloat(value) > 0 ? "red" : "green";
1483}
1484$(function() {
1485    if ($("#fundxq").length > 0) {
1486        var sl = $(".ti-left .code").text()
1487        var fundcode = sl ? sl.slice(1, sl.length - 1) : ""
1488        if (!fundcode) {
1489            var pathMatch = window.location.pathname.match(/\/(?:jj|hq)\/([0-9a-zA-Z]{6})/i)
1490            if (pathMatch) {
1491                fundcode = pathMatch[1]
1492            }
1493        }
1494        var fundtype = $(".ti-left .tips").text()
1495        // 大图入口 /chart/{code}?market=f
1496        if (fundcode && !$("#etfChartBigLink").length) {
1497            var $slot = $(".ti-left").first();
1498            if ($slot.length) {
1499                $slot.append(' <a id="etfChartBigLink" href="/chart/' + fundcode + '?market=f" target="_blank" style="margin-left:8px;font-size:12px;color:#2962ff;">大图</a>');
1500            }
1501        }
1502        var page_title_id = $("#fundxq").attr("page-title-id")
1503        var currentTime = $("#gstime").text()
1504        // 🔥 注释掉:SSR 已经正确设置了净值估算时间,不需要客户端设置日期
1505        // $("#currTime").html(formatDate(new Date(), 3))
1506        $("#new-sidebar a, #fund-d-menu a").each(function() {
1507            var _this = $(this)
1508            var match = _this.attr("id") == page_title_id
1509            _this.toggleClass("active", match)
1510            _this.toggleClass("on", match && !_this.closest("#new-sidebar").length)
1511            _this.removeClass("active-parent")
1512        })
1513        var navGroupMap = {
1514            f_menu_jdzf: "yjfx",
1515            f_menu_ldcp: "yjfx",
1516            f_menu_jjgs: "jjgs",
1517            f_menu_zcpz: "zcpz",
1518            f_menu_zcpz_fx: "zcpz",
1519            f_menu_hypz: "hypz",
1520            f_menu_hypz_fx: "hypz",
1521            f_menu_zcg_ss: "zcg",
1522            f_menu_zcg_mx: "zcg",
1523            f_menu_zcj_mx: "zcg",
1524            f_menu_ccjd: "zcg"
1525        }
1526        var parentGroup = navGroupMap[page_title_id]
1527        if (parentGroup) {
1528            $("#new-sidebar a[data-nav-group='" + parentGroup + "']").addClass("active-parent")
1529        }
1530        // 纯 JavaScript 实现:仅绑定主图表箱 .hq-types(净值走势图、实时估值、重仓持股)
1531        // 避免把下方货币箱「万份收益/年化收益率」算进同一套 index
1532        var mainHqBox = document.querySelector(".hq-img-box");
1533        if (mainHqBox) {
1534            var tabContainer = mainHqBox.querySelector(".hq-types");
1535            if (tabContainer) {
1536                var tabButtons = tabContainer.querySelectorAll("li");
1537                tabButtons.forEach(function(button, index) {
1538                    button.addEventListener('click', function() {
1539                        var allButtons = Array.from(tabContainer.querySelectorAll('li'));
1540                        var contentItems = Array.from(tabContainer.parentElement.querySelectorAll('.fund-tab-content-item'));
1541
1542                        allButtons.forEach(function(btn) {
1543                            btn.classList.remove('cur');
1544                        });
1545                        this.classList.add('cur');
1546
1547                        contentItems.forEach(function(item, idx) {
1548                            if (idx === index) {
1549                                item.style.display = '';
1550                            } else {
1551                                item.style.display = 'none';
1552                            }
1553                        });
1554
1555                        if (this.textContent.trim() === "实时估值") {
1556                            // 有缓存则先同步画正式分时,避免空窗/怪过渡;再拉取刷新
1557                            try { paintGuzhiChartFromCache(fundcode); } catch (ePaintCache) {}
1558                            setTimeout(function() {
1559                                var jzgs = document.getElementById("jzgs");
1560                                var gzChart = document.getElementById("gz-chart");
1561                                var isJzgsVisible = jzgs &&
1562                                    (jzgs.offsetWidth > 0 || jzgs.offsetHeight > 0) &&
1563                                    window.getComputedStyle(jzgs).display !== 'none';
1564                                var isGzChartVisible = gzChart &&
1565                                    (gzChart.offsetWidth > 0 || gzChart.offsetHeight > 0) &&
1566                                    window.getComputedStyle(gzChart).display !== 'none';
1567                                if (isJzgsVisible || isGzChartVisible) {
1568                                    GetchartMgzzs(fundcode);
1569                                } else {
1570                                    setTimeout(function() { GetchartMgzzs(fundcode); }, 300);
1571                                }
1572                            }, 100);
1573                        }
1574                    });
1575                });
1576            }
1577        }
1578        // 个基净值走势
1579        if ($("#jz-chart").length > 0) {
1580            chartjzzs(fundcode)
1581        }
1582        if ($(".valuat-hq-nav").length > 0) {
1583            initPcNavTimeTabs()
1584        }
1585        if (fundtype.indexOf("货币型") >= 0) {
1586            $(".hq-img-box").eq(1).show()
1587            $(".hq-img-box").eq(0).hide()
1588
1589
1590            // 个基自选
1591            iszx(fundcode, 2)
1592            $(".ti-right .GJ_ad_zx_btn").click(function() {
1593                if ($(this).hasClass("on")) {
1594                    delzx(fundcode, 2)
1595                } else {
1596                    issetzx(fundcode, 2)
1597                }
1598            })
1599        } else {
1600            $(".hq-img-box").eq(0).show()
1601            $(".hq-img-box").eq(1).hide()
1602            var pageTitleId = $("#fundxq").attr("page-title-id") || "";
1603            if (pageTitleId !== "f_menu_index" && ($("#fund-estimate").length > 0 || $("#currTime").length > 0)) {
1604                setTimeout(function() {
1605                    loadFundGuzhiHeader(fundcode);
1606                }, 300);
1607            }
1608                // 个基实时估值走势图=》(使用 HTTP 轮询方式)
1609            // 🔥 SSE 已禁用,使用 HTTP 轮询方式更新数据
1610            if ($("#jzgs").length > 0 || $("#gz-chart").length > 0 || $("#gsvalue").length > 0 || $("#ZFgsvalue1").length > 0) {
1611                GetchartMgzzs(fundcode)
1612                // 🔥 SSE 已禁用,使用 HTTP 轮询方式定期更新
1613                // 设置定时轮询更新(每5秒更新一次)
1614                setInterval(function() {
1615                    if ($("#jzgs").length > 0 || $("#gz-chart").length > 0 || $("#gsvalue").length > 0 || $("#ZFgsvalue1").length > 0) {
1616                        GetchartMgzzs(fundcode);
1617                    }
1618                }, 5000); // 每5秒轮询一次
1619            }
1620
1621            if ($("#gpjbRQ,.jj_ZCCG,.jj_ZCCG_MX").length > 0) GSgpbaojia(fundcode)
1622
1623            // 个基自选
1624            iszx(fundcode, 1)
1625            $(".ti-right .GJ_ad_zx_btn").click(function() {
1626                if ($(this).hasClass("on")) {
1627                    delzx(fundcode, 1)
1628                } else {
1629                    issetzx(fundcode, 1)
1630                }
1631            })
1632        }
1633
1634        // 个基首页
1635        if ($("#fundxq[page-title-id='f_menu_index']").length > 0) {
1636            chartsZCPZ(fundcode)
1637                // chartsCPy(fundcode)
1638            chartsHYPZ(fundcode)
1639        }
1640        // 🔥 优化:移除自动调用,让模板完全控制图表加载时机,避免重复请求
1641        // 模板中的 setDefaultTo1Year 会调用 zschanrt,进而调用 chartsJZZS
1642        // if ($("#valuat-hq").length > 0 || $("#fund_hb_nhsy").length > 0) {
1643        //     chartsJZZS(fundcode, "1y")
1644        // }
1645
1646        if ($("#spider_chart").length > 0) {
1647            var spiderData = [{
1648                    name: '半年测评',
1649                    data: [4, 1, 6],
1650                    pointPlacement: 'on'
1651
1652                },
1653                {
1654                    name: '一年测评',
1655                    data: [5, 1, 10],
1656                    pointPlacement: 'on'
1657                },
1658                {
1659                    name: '两年测评',
1660                    data: [3, 6, 9],
1661                    pointPlacement: 'on'
1662                },
1663            ]
1664            loadspiderChart("spider_chart", spiderData)
1665        }
1666    }
1667
1668})
1669
1670function chartsHYPZ(fundcode) {
1671    Sendrequest('/fundcode/getfundindexhangyepeizhi?fundcode=' + fundcode, null, function success(data) {
1672        if (data.successcode == '1') {
1673            var obj = data.data,
1674                list = obj.hydata,
1675                date = obj.riqilist[0]
1676            var chartdata = [],
1677                cd1 = []
1678            $.each(list, function(index, i) {
1679                cd1.push({ name: i.hymc, y: Number(i.jzbl) })
1680            })
1681            chartdata.push({
1682                name: "占净值比例",
1683                colorByPoint: true,
1684                size: '60%',
1685                data: cd1,
1686            })
1687            loadbarChart("fd_hypzcp", chartdata, { height: 320, pieRadiusRatio: 0.44, innerRadiusRatio: 0.58, piePalette: 'warm' })
1688            $(".fund-d-c-content .public2-title span:contains('行业配置')").parent().siblings(".gzmesstime").append(date)
1689
1690        } else {
1691            $("#fd_hypzcp").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>暂无数据</p>")
1692        }
1693    }, function fail() {
1694        $("#fd_hypzcp").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>请求失败</p>")
1695    })
1696}
1697
1698function chartsCPy(fundcode) {
1699    Sendrequest('/fundcode/getfundindexcompcheping?fundcode=' + fundcode, null, function success(data) {
1700        if (data.successcode == '1') {
1701            var list = data.data
1702            var chartdata = []
1703            var cd1 = []
1704            $.each(list, function(index, i) {
1705                cd1.push([i.f001, i.number])
1706            })
1707            chartdata.push({
1708                data: cd1,
1709            })
1710            loadColumnChart("comchart", "各公司旗下基金数量比较图", chartdata, false, "个")
1711
1712        } else {
1713            $("#comchart").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>暂无数据</p>")
1714        }
1715    }, function fail() {
1716        $("#comchart").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>请求失败</p>")
1717    })
1718}
1719
1720function chartsZCPZ(fundcode) {
1721    var chartdata = []
1722    Sendrequest('/fundcode/getfundindexzicanpeizhi?fundcode=' + fundcode, null, function success(data) {
1723        if (data.successcode == '1') {
1724            var obj = data.data
1725            var list = obj.peidata.data,
1726                date = obj.riqilist[0]
1727            var cd1 = []
1728            $.each(list, function(index, i) {
1729                cd1.push({ name: i.mc, y: Number(i.bl) })
1730            })
1731            chartdata.push({
1732                name: "占净值比例",
1733                colorByPoint: true,
1734                size: '60%',
1735                data: cd1
1736            })
1737            loadbarChart("fd_zcpzcp", chartdata, { height: 320, pieRadiusRatio: 0.44, innerRadiusRatio: 0.58, piePalette: 'warm' })
1738            $(".fund-d-c-content .public2-title span:contains('资产
1738配置')").parent().siblings(".gzmesstime").append(date)
1739        } else {
1740            $("#fd_zcpzcp").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>暂无数据</p>")
1741        }
1742    }, function fail() {
1743        $("#fd_zcpzcp").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>请求失败</p>")
1744    })
1745
1746
1747}
1748
1749// 全局变量,用于存储图表实例
1750var currentChartInstance = null;
1751// 🔥 优化:添加防重复调用标志,避免短时间内重复请求
1752var chartLoadingFlag = null; // 格式:{fundcode: 'xxx', type: '1y', timestamp: 1234567890}
1753
1754function initPcNavTimeTabs() {
1755    if (!window.jQuery) return
1756    var $ = window.jQuery
1757    var tabs = [
1758        { type: '1y', label: '1å¹´' },
1759        { type: '2y', label: '2å¹´' },
1760        { type: '3y', label: '3å¹´' },
1761        { type: '4y', label: '4å¹´' },
1762        { type: '5y', label: '5å¹´' },
1763        { type: 'max', label: '成立以来' }
1764    ]
1765    $('.valuat-hq-nav ul').each(function () {
1766        var $ul = $(this)
1767        $ul.empty()
1768        tabs.forEach(function (t, i) {
1769            var cls = i === 0 ? ' class="active"' : ''
1770            $ul.append('<li' + cls + ' onclick="zschanrt(\'' + t.type + '\',this)">' + t.label + '</li>')
1771        })
1772    })
1773}
1774
1775function chartsJZZS(fundcode, type) {
1776    var fundname = $(".ti-left .name").text()
1777    type = type || '1y'
1778
1779    if (window.FundCharts && FundCharts.getNavCacheEntry(fundcode)) {
1780        if (FundCharts.applyNavFromCache(fundcode, type)) {
1781            if (chartLoadingFlag) chartLoadingFlag = null
1782            return
1783        }
1784    }
1785
1786    var hasCache = window.FundCharts && FundCharts.getNavCacheEntry(fundcode)
1787    var fetchType = hasCache ? type : 'max'
1788    var hasquery = '&sjtype=' + fetchType
1789    
1790    // 🔥 优化:防重复调用逻辑(增强版)
1791    var currentKey = fundcode + '_' + (type || 'default');
1792    var now = Date.now();
1793    
1794    // 如果正在加载相同的图表(1000ms内),直接返回
1795    if (chartLoadingFlag && 
1796        chartLoadingFlag.key === currentKey && 
1797        (now - chartLoadingFlag.timestamp) < 1000) {
1798        console.log('防重复调用:正在加载相同的图表,跳过本次请求', currentKey, '距离上次请求:', (now - chartLoadingFlag.timestamp) + 'ms');
1799        return;
1800    }
1801    
1802    // 设置加载标志
1803    chartLoadingFlag = {
1804        key: currentKey,
1805        timestamp: now
1806    };
1807    
1808    console.log('开始加载图表:', currentKey, '时间戳:', now, 'API区间:', fetchType);
1809    
1810    console.log('切换时间范围:', type, '基金代码:', fundcode);
1811    
1812    Sendrequest('/fundcode/getfundindexjingzhizoushi?fundcode=' + fundcode + hasquery, null, function success(data) {
1813        // 请求完成后清除加载标志
1814        if (chartLoadingFlag && chartLoadingFlag.key === currentKey) {
1815            chartLoadingFlag = null;
1816        }
1817        console.log('API响应数据:', data, "完整响应-------------")
1818        console.log('successcode:', data ? data.successcode : 'undefined', '数据类型:', typeof(data ? data.successcode : 'undefined'))
1819        console.log('data字段:', data ? data.data : 'undefined', 'data是否为数组:', Array.isArray(data ? data.data : null))
1820        
1821        // 检查数据是否存在
1822        if (!data) {
1823            console.error('❌ API返回数据为空!')
1824            $("#valuat-hq").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>返回数据为空</p>")
1825            $("#fund_hb_nhsy").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>返回数据为空</p>")
1826            return
1827        }
1828        
1829        // 如果服务器返回了实际使用的时间范围,更新按钮状态
1830        if (data.used_time_range) {
1831            var actualType = data.used_time_range;
1832            console.log('服务器返回的实际时间范围:', actualType);
1833            if (window.FundCharts && FundCharts.highlightNavButton) {
1834                FundCharts.highlightNavButton(actualType);
1835            }
1836        }
1837        
1838        if (data.successcode == '1') {
1839            console.log('✅ successcode为1,开始处理数据')
1840            var list = data.data
1841            console.log('数据列表长度:', list ? list.length : 0)
1842            if (!list || list.length === 0) {
1843                console.warn('⚠️ 数据列表为空!')
1844                $("#valuat-hq").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>数据列表为空</p>")
1845                $("#fund_hb_nhsy").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>数据列表为空</p>")
1846                return
1847            }
1848            if (window.FundCharts && FundCharts.buildNavCacheFromList) {
1849                var cacheEntry = FundCharts.buildNavCacheFromList(list, fundcode, isHB())
1850                if (cacheEntry) FundCharts.storeNavCache(fundcode, cacheEntry)
1851            }
1852            var chartdata = []
1853            if (isHB()) {
1854                var date = 0,
1855                    fqjz = [],
1856                    fundData = [],
1857                    ljjz = [],
1858                    fqzf = []
1859                $.each(list, function(index, i) {
1860                    date = new Date(i.declaredate.replace(/-/g, '/')).getTime()
1861                    fundData.push([date, Number(i.f002)])
1862                    ljjz.push([date, Number(i.f003)])
1863                    fqjz.push([date, Number(i.f008)])
1864                    fqzf.push([date, Number(i.zf)])
1865                })
1866                chartdata.push({ name: "复权净值", data: fqjz, color: "red" })
1867                loadHbLineChart("valuat-hq", "", chartdata, true, true, fundData, ljjz, fundcode)
1868                if (window.FundCharts) FundCharts.setNavTimeRange("valuat-hq", type)
1869            } else {
1870                var date = 0,
1871                    wfsy = []
1872                $.each(list, function(index, i) {
1873                    date = new Date(i.declaredate.replace(/-/g, '/')).getTime()
1874                    wfsy.push([date, Number(i.f002)])
1875                })
1876                chartdata.push({
1877                    name: "万份收益",
1878                    data: wfsy,
1879                    yAxis: 0,
1880                    color: "red",
1881                    tooltip: {
1882                        valueSuffix: '元'
1883                    }
1884                })
1885                var cid = $("#fund_hb_nhsy").length > 0 ? "fund_hb_nhsy" : "valuat-hq"
1886                loadHbnhsyLineChart(cid, "", chartdata, fundcode)
1887                if (window.FundCharts) FundCharts.setNavTimeRange(cid, type)
1888            }
1889
1890        } else {
1891            console.warn('⚠️ successcode不是1,当前值:', data.successcode, '类型:', typeof data.successcode)
1892            console.warn('完整响应:', JSON.stringify(data, null, 2))
1893            $("#valuat-hq").html("<p style='text-align:center;font-size:26px;
1893color:#777;line-height:400px'>暂无数据</p>")
1894            $("#fund_hb_nhsy").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>暂无数据</p>")
1895        }
1896        
1897        // 请求完成后清除加载标志
1898        if (chartLoadingFlag && chartLoadingFlag.key === currentKey) {
1899            chartLoadingFlag = null;
1900        }
1901    }, function fail(xhr, status, error) {
1902        // 请求失败后也要清除加载标志
1903        if (chartLoadingFlag && chartLoadingFlag.key === currentKey) {
1904            chartLoadingFlag = null;
1905        }
1906        
1907        console.error('❌ API请求失败!')
1908        console.error('HTTP状态码:', xhr ? xhr.status : 'unknown')
1909        console.error('状态:', status)
1910        console.error('错误:', error)
1911        console.error('响应文本:', xhr ? xhr.responseText : 'unknown')
1912        
1913        var errorMsg = '请求失败'
1914        if (xhr && xhr.status) {
1915            errorMsg += ' (HTTP ' + xhr.status + ')'
1916        }
1917        if (error) {
1918            errorMsg += ': ' + error
1919        }
1920        
1921        $("#valuat-hq").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>" + errorMsg + "</p>")
1922        $("#fund_hb_nhsy").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>" + errorMsg + "</p>")
1923    })
1924}
1925
1926function isHB() {
1927    var fundtype = $(".ti-left .tips").text()
1928    if (fundtype == "货币型") {
1929        return false
1930    } else {
1931        return true
1932    }
1933}
1934
1935// 多市场交易时段(A/港/美本地钟)+ WWW 重仓轮询闸门
1936/** 报价市场:A|HK|US(持仓代码推断) */
1937function resolveQuoteMarketKind(code) {
1938    var c = String(code || '').trim().toUpperCase();
1939    if (!c) return 'A';
1940    if (/^US[./]?/.test(c)) return 'US';
1941    if (/^HK/.test(c) || c === 'HSI' || c === 'HSTECH' || c === 'HSCEI') return 'HK';
1942    if (/^(SH|SZ|BJ)/.test(c)) return 'A';
1943    var dig = c.replace(/\D/g, '');
1944    if (/^[0-9]{5}$/.test(dig)) return 'HK';
1945    if (/^[0-9]{6}$/.test(dig)) return 'A';
1946    if (/^[A-Z]{1,5}$/.test(c)) return 'US';
1947    return 'A';
1948}
1949
1950/** 市场本地钟点 {day:0-6, hm:930};US=美东,HK=港,A=沪 */
1951function getMarketLocalClock(marketKind) {
1952    var kind = String(marketKind || 'A').toUpperCase();
1953    var tz = kind === 'US' ? 'America/New_York' : (kind === 'HK' ? 'Asia/Hong_Kong' : 'Asia/Shanghai');
1954    try {
1955        var parts = new Intl.DateTimeFormat('en-US', {
1956            timeZone: tz,
1957            weekday: 'short',
1958            hour: '2-digit',
1959            minute: '2-digit',
1960            hour12: false
1961        }).formatToParts(new Date());
1962        var map = {};
1963        for (var i = 0; i < parts.length; i++) map[parts[i].type] = parts[i].value;
1964        var wd = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
1965        var day = wd[map.weekday];
1966        if (day == null) day = new Date().getDay();
1967        var hour = parseInt(map.hour, 10);
1968        var minute = parseInt(map.minute, 10);
1969        if (hour === 24) hour = 0;
1970        return { day: day, hm: hour * 100 + minute, tz: tz };
1971    } catch (e) {
1972        var now = new Date();
1973        return { day: now.getDay(), hm: now.getHours() * 100 + now.getMinutes(), tz: tz };
1974    }
1975}
1976
1977function getQuoteSessionRanges(marketKind) {
1978    var kind = String(marketKind || 'A').toUpperCase();
1979    if (kind === 'HK') return [[930, 1200], [1300, 1600]];
1980    if (kind === 'US') return [[930, 1600]];
1981    return [[930, 1130], [1300, 1500]];
1982}
1983
1984
1985/** 2026 交易所休市日(YYYY-MM-DD);周末另判。A 不含补班周末(股市周末永不交易) */
1986var MARKET_HOLIDAYS_2026 = {
1987    A: {
1988        '2026-01-01':1,'2026-01-02':1,
1989        '2026-02-16':1,'2026-02-17':1,'2026-02-18':1,'2026-02-19':1,'2026-02-20':1,'2026-02-23':1,
1990        '2026-04-06':1,
1991        '2026-05-01':1,'2026-05-04':1,'2026-05-05':1,
1992        '2026-06-19':1,
1993        '2026-09-25':1,
1994        '2026-10-01':1,'2026-10-02':1,'2026-10-05':1,'2026-10-06':1,'2026-10-07':1
1995    },
1996    HK: {
1997        '2026-01-01':1,
1998        '2026-02-16':1,'2026-02-17':1,'2026-02-18':1,
1999        '2026-04-03':1,'2026-04-06':1,'2026-04-07':1,
2000        '2026-05-01':1,'2026-05-25':1,
2001        '2026-06-19':1,
2002        '2026-07-01':1,
2003        '2026-10-01':1,'2026-10-19':1,
2004        '2026-12-25':1,'2026-12-26':1
2005    },
2006    US: {
2007        '2026-01-01':1,'2026-01-19':1,'2026-02-16':1,
2008        '2026-04-03':1,
2009        '2026-05-25':1,'2026-06-19':1,'2026-07-03':1,
2010        '2026-09-07':1,'2026-11-26':1,'2026-12-25':1
2011    }
2012};
2013
2014function getMarketLocalDateStr(marketKind) {
2015    var kind = String(marketKind || 'A').toUpperCase();
2016    var tz = kind === 'US' ? 'America/New_York' : (kind === 'HK' ? 'Asia/Hong_Kong' : 'Asia/Shanghai');
2017    try {
2018        var parts = new Intl.DateTimeFormat('en-CA', {
2019            timeZone: tz,
2020            year: 'numeric',
2021            month: '2-digit',
2022            day: '2-digit'
2023        }).formatToParts(new Date());
2024        var map = {};
2025        for (var i = 0; i < parts.length; i++) map[parts[i].type] = parts[i].value;
2026        return map.year + '-' + map.month + '-' + map.day;
2027    } catch (e) {
2028        var d = new Date();
2029        var m = (d.getMonth() + 1);
2030        var day = d.getDate();
2031        return d.getFullYear() + '-' + (m < 10 ? '0' : '') + m + '-' + (day < 10 ? '0' : '') + day;
2032    }
2033}
2034
2035function isMarketHoliday(marketKind, dateStr) {
2036    var kind = String(marketKind || 'A').toUpperCase();
2037    var ds = dateStr || getMarketLocalDateStr(kind);
2038    var bag = MARKET_HOLIDAYS_2026[kind] || {};
2039    return !!bag[ds];
2040}
2041
2042function isMarketTradingNow(marketKind) {
2043    var clock = getMarketLocalClock(marketKind);
2044    if (clock.day === 0 || clock.day === 6) return false;
2045    if (isMarketHoliday(marketKind)) return false;
2046    var ranges = getQuoteSessionRanges(marketKind);
2047    for (var i = 0; i < ranges.length; i++) {
2048        if (clock.hm >= ranges[i][0] && clock.hm <= ranges[i][1]) return true;
2049    }
2050    return false;
2051}
2052
2053function filterCodesInOpenMarket(codes) {
2054    var list = Array.isArray(codes) ? codes : [];
2055    var out = [];
2056    for (var i = 0; i < list.length; i++) {
2057        if (isMarketTradingNow(resolveQuoteMarketKind(list[i]))) out.push(list[i]);
2058    }
2059    return out;
2060}
2061
2062function isTradingTime() {
2063    // 兼容旧名:默认按 A 股判断
2064    return isMarketTradingNow('A');
2065}
2066
2067// WWW 重仓股实时涨跌:本站 Redis32;非交易时段不发请求
2068var _wwwZcgQuoteTimer = null
2069function freshgp() {
2070    var stockcodes = []
2071    var stockRows = []
2072    $("#gsstockBJ tbody tr,.jj_ZCCG tbody tr").each(function() {
2073        var raw = String($(this).find("td:eq(1)").text() || '').trim().toUpperCase()
2074        var stockcode = raw.replace(/\D/g, '')
2075        if (!stockcode && /^[A-Z]{1,5}$/.test(raw)) stockcode = raw
2076        if (!stockcode) return
2077        if (/^\d+$/.test(stockcode)) {
2078            if (stockcode.length <= 5) {
2079                while (stockcode.length < 5) stockcode = '0' + stockcode
2080            } else {
2081                while (stockcode.length < 6) stockcode = '0' + stockcode
2082                stockcode = stockcode.slice(-6)
2083            }
2084        }
2085        if (stockcodes.indexOf(stockcode) < 0) stockcodes.push(stockcode)
2086        stockRows.push({ row: this, code: stockcode })
2087    })
2088    if (!stockcodes.length) return
2089
2090    var openCodes = filterCodesInOpenMarket(stockcodes)
2091    if (!openCodes.length) return
2092    var openSet = {}
2093    for (var i = 0; i < openCodes.length; i++) openSet[openCodes[i]] = 1
2094
2095    $.ajax({
2096        type: "get",
2097        url: "/stock/quotes?codes=" + encodeURIComponent(openCodes.join(',')) + "&_=" + Date.now(),
2098        dataType: 'json',
2099        timeout: 8000
2100    }).done(function(result) {
2101        var data = (result && result.data) ? result.data : null
2102        if (!data) return
2103        stockRows.forEach(function(item) {
2104            if (!openSet[item.code]) return
2105            var q = data[item.code] || data['SZ' + item.code] || data['SH' + item.code] || data['HK' + item.code] || null
2106            if (!q) return
2107            var zfNum = (q.zf != null && isFinite(Number(q.zf))) ? Number(q.zf) 
2107: NaN
2108            var zf = isFinite(zfNum) ? (zfNum.toFixed(2) + '%') : '--'
2109            if ($(".jj_ZCCG").length > 0) {
2110                $(item.row).find("td:eq(3)").html(zf).removeClass("red green").addClass(changecolor(zf))
2111            }
2112            if ($("#gsstockBJ").length > 0) {
2113                $(item.row).find("td:eq(4)").html(zf).removeClass("red green").addClass(changecolor(zf))
2114            }
2115        })
2116    }).fail(function() {
2117        console.log('获取股票实时数据失败')
2118    })
2119}
2120function startWwwZcgQuotePoll() {
2121    if (_wwwZcgQuoteTimer) clearInterval(_wwwZcgQuoteTimer)
2122    // 定时器可常挂:休市时 freshgp 直接 return,开市自动恢复
2123    freshgp()
2124    _wwwZcgQuoteTimer = setInterval(freshgp, 5000)
2125}
2126
2127
2128function formatGpStockCells(code, name) {
2129    var c = String(code == null ? '' : code).replace(/[^0-9A-Za-z]/g, '').slice(0, 6);
2130    var n = name == null ? '' : String(name);
2131    if (!c) {
2132        return { codeHtml: n || '---', nameHtml: n || '---' };
2133    }
2134    var href = '/chart/' + c + '?market=a';
2135    var codeHtml = '<a href="' + href + '" target="_blank" rel="noopener">' + String(code) + '</a>';
2136    var nameHtml = '<a href="' + href + '" target="_blank" rel="noopener">' + (n || c) + '</a>';
2137    return { codeHtml: codeHtml, nameHtml: nameHtml };
2138}
2139
2140function GSgpbaojia(fundcode, jbrq) {
2141    var zcgTable = $(".jj_ZCCG tbody")
2142    var zcgMxTable = $(".jj_ZCCG_MX tbody")
2143    zcgTable.html('')
2144    zcgMxTable.html('')
2145    $(".public2-title:contains('基金重仓股实时报价')").find(".pub2-more2").text("")
2146    var jbrqParam = jbrq ? ('&jbrq=' + encodeURIComponent(jbrq)) : ''
2147    Sendrequest('/fundcode/getmfundjihinchicang?fundcode=' + fundcode + jbrqParam, null, function success(data) {
2148        if (data.successcode == '1' && data.data) {
2149            var list = data.data.result || []
2150            var jbrq = data.data.jbrq || ''
2151            var jbrqList = data.data.jbrqList || []
2152            var html = ""
2153            var htmlMx = ""
2154
2155            $.each(list, function(idx, i) {
2156                var zf = i.zf || "--"
2157                var holdChangeText = resolveHoldChangeTextWithRatio(i.bd, i.jsq)
2158                var bdcolor = resolveHoldChangeColorClass(i.jsq)
2159
2160                if ($("#fundxq[page-title-id='f_menu_index']").length > 0) {
2161                    var gpCells = formatGpStockCells(i.stockcode, i.stockname); html += '<tr><td>' + (idx + 1) + '</td><td>' + gpCells.codeHtml + '</td><td>' + gpCells.nameHtml + '</td><td class="' + changecolor(zf) + '">' + zf + '</td><td>' + (i.f003 || "---") + '</td><td>' + (i.f002 || "---") + '</td><td class="' + bdcolor + '">' + holdChangeText + '</td></tr>'
2162                }
2163                if ($("#fundxq[page-title-id='f_menu_jjgz']").length > 0) {
2164                    var gpCells = formatGpStockCells(i.stockcode, i.stockname); html += '<tr><td>' + (idx + 1) + '</td><td>' + gpCells.codeHtml + '</td><td>' + gpCells.nameHtml + '</td><td class="' + changecolor(zf) + '">' + zf + '</td><td>' + (i.f003 || "---") + '</td><td>' + (i.f002 || "---") + '</td><td class="' + bdcolor + '">' + holdChangeText + '</td></tr>'
2165                }
2166                if ($("#fundxq[page-title-id='f_menu_zcg_ss']").length > 0) {
2167                    var gpCells = formatGpStockCells(i.stockcode, i.stockname); html += '<tr><td>' + (idx + 1) + '</td><td>' + gpCells.codeHtml + '</td><td>' + gpCells.nameHtml + '</td><td class="' + changecolor(zf) + '">' + zf + '</td><td>' + (i.f003 || "---") + '</td><td>' + (i.f001 || "---") + '</td><td>' + (i.f002 || "---") + '</td><td>' + (i.f004 || "---") + '</td><td class="' + bdcolor + '">' + holdChangeText + '</td></tr>'
2168                }
2169                if ($("#fundxq[page-title-id='f_menu_zcg_mx']").length > 0) {
2170                    var gpCellsMx = formatGpStockCells(i.stockcode, i.stockname); htmlMx += '<tr><td>' + (idx + 1) + '</td><td>' + gpCellsMx.codeHtml + '</td><td>' + gpCellsMx.nameHtml + '</td><td>' + (i.f003 || "---") + '</td><td>' + (i.f001 || "---") + '</td><td>' + (i.f002 || "---") + '</td><td>' + (i.f004 || "---") + '</td><td class="' + bdcolor + '">' + holdChangeText + '</td></tr>'
2171                }
2172            })
2173
2174            if (jbrq) {
2175                $("#gpjbRQ").html(jbrq)
2176                $("#jj_ZCCG_time").html("截止日期:" + jbrq)
2177                if ($("#fundxq[page-title-id='f_menu_zcg_mx']").length > 0) {
2178                    var selectEl = $("#jj_ZCCG_time_mx_select")
2179                    if (selectEl.length > 0) {
2180                        selectEl.empty()
2181                        if (jbrqList && jbrqList.length > 0) {
2182                            $.each(jbrqList, function(_, dateVal) {
2183                                var option = $('<option></option>').val(dateVal).text(dateVal)
2184                                if (dateVal === jbrq) {
2185                                    option.attr('selected', 'selected')
2186                                }
2187                                selectEl.append(option)
2188                            })
2189                        } else {
2190                            selectEl.append($('<option></option>').val(jbrq).text(jbrq))
2191                        }
2192                        selectEl.off('change').on('change', function() {
2193                            var selected = $(this).val()
2194                            if (selected) {
2195                                GSgpbaojia(fundcode, selected)
2196                            }
2197                        })
2198                    }
2199                }
2200            }
2201
2202            if (zcgTable.length > 0) {
2203                zcgTable.html(html)
2204            }
2205            if (zcgMxTable.length > 0) {
2206                zcgMxTable.html(htmlMx)
2207            }
2208            // 首页净值走势旁重仓 / 重仓股页:启动本地行情轮询
2209            if ($(".jj_ZCCG tbody tr").length > 0 || $("#gsstockBJ tbody tr").length > 0) {
2210                startWwwZcgQuotePoll()
2211            }
2212
2213        } else {
2214            newtable("暂无查询结果!", $(".jj_ZCCG,.jj_ZCCG_MX"));
2215            if ($("#fundxq[page-title-id='f_menu_zcg_mx']").length > 0) {
2216                var selectEl = $("#jj_ZCCG_time_mx_select")
2217                if (selectEl.length > 0) {
2218                    selectEl.empty()
2219                }
2220            }
2221        }
2222    }, function fail() {
2223        newtable("查询失败,请稍后再试!", $(".jj_ZCCG,.jj_ZCCG_MX"));
2224        if ($("#fundxq[page-title-id='f_menu_zcg_mx']").length > 0) {
2225            var selectEl = $("#jj_ZCCG_time_mx_select")
2226            if (selectEl.length > 0) {
2227                selectEl.empty()
2228            }
2229        }
2230    })
2231}
2232
2233function resolveHoldChangeTextWithRatio(bd, jsq) {
2234    // 与 M 较上期统一:按 jsq → 新增 / 不变 / ±.xx%;禁止 0.00% 与误用 bd===0→新增
2235    if (jsq === '新增' || jsq === '不变' || jsq === '---' || jsq === '--') {
2236        return jsq === '--' ? '---' : jsq
2237    }
2238    if (jsq === null || jsq === undefined || jsq === '') {
2239        if (bd === '新增' || bd === null || bd === undefined || bd === '' || bd === '---') {
2240            return '新增'
2241        }
2242        // jsq 缺失时不再用 bd 拼增仓/减仓,统一为新增(无上期可比)
2243        return '新增'
2244    }
2245    var jsqNum = parseFloat(String(jsq).replace(/%/g, ''))
2246    if (isNaN(jsqNum)) {
2247        return '---'
2248    }
2249    if (jsqNum === 0) {
2250        return '不变'
2251    }
2252    return jsqNum.toFixed(2) + '%'
2253}
2254
2255function resolveHoldChangeColorClass(jsq) {
2256    if (jsq === null || jsq === undefined || jsq === '' || jsq === '新增' || jsq === '不变' || jsq === '---' || jsq === '--') {
2257        return 'green'
2258    }
2259    var jsqNum = parseFloat(String(jsq).replace(/%/g, ''))
2260    if (isNaN(jsqNum) || jsqNum === 0) {
2261        return 'green'
2262    }
2263    return jsqNum > 0 ? 'red' : 'green'
2264}
2265
2266 
2267
2268// 查找数组中最小值
2269function arrayMin(arrs) {
2270    var min = arrs[0];
2271    for (var i = 1, ilen = arrs.length; i < ilen; i += 1) {
2272        if (arrs[i] < min) {
2273            min = arrs[i];
2274        }
2275    }
2276    return min;
2277}
2278// 在数组中查找最大值
2279function arrayMax(arrs) {
2280    // 🔥 修复:处理空数组或 undefined 的情况
2281    if (!arrs || arrs.length === 0) {
2282        return 1; // 返回默认值
2283    }
2284    // Math.abs() 取绝对值
2285    var max = Math.abs(arrs[0] || 0);
2286    for (var i = 1, ilen = arrs.length; i < ilen; i++) {
2287        var absValue = Math.abs(arrs[i] || 0);
2288        if (absValue > max) {
2289            max = absValue;
2290        }
2291    }
2292    return max || 1; // 如果 max 为 0 或 NaN,返回默认值 1
2293}
2294
2295function chartjzzs(fundcode) {
2296    var dwjz = [],
2297        ljjz = []
2298    Sendrequest('/fundcode/getfundjingzhizoushi?fundcode=' + fundcode, null, function success(data) {
2299        if (data.successcode == '1') {
2300            var list = data.data
2301            $.each(list, function(index, i) {
2302                dwjz.push([new Date(i.declaredate).getTime(), Number(i.f002)])
2303                ljjz.push([new Date(i.declaredate).getTime(), Number(i.f003)])
2304            })
2305            var series = [
2306                { name: '单位净值', data: dwjz },
2307                { name: '累计净值', data: ljjz, color: "red" },
2308
2309            ]
2310            loadHbLineChart("jz-chart", "", series)
2311
2312        }
2313    }, function fail() {})
2314}
2315
2316// 🔥 使用 SSE 实时推送估值数据
2317var valuationEventSource = null;
2318// 🔥 防重复调用标志
2319var sseConnectingFlag = null; // 格式:{fundcode: 'xxx', timestamp: 1234567890}
2320
2321// 添加视觉反馈样式(如果尚未添加)
2322if (!document.getElementById('valuation-feedback-styles')) {
2323    var style = document.createElement('style');
2324    style.id = 'valuation-feedback-styles';
2325    style.textContent = `
2326        .valuation-updated {
2327            background-color: #ffffcc !important;
2328            color: #ff6600 !important;
2329            font-weight: bold !important;
2330            transition: all 0.3s ease !important;
2331            animation: valuationBlink 0.5s ease-in-out 2;
2332        }
2333        @keyframes valuationBlink {
2334            0%, 100% { opacity: 1; }
2335            50% { opacity: 0.7; }
2336        }
2337    `;
2338    document.head.appendChild(style);
2339}
2340
2341function GetchartMgzzsSSE(fundcode) {
2342    // 🔥 SSE 已禁用,此函数不再使用,直接返回
2343    console.log('⚠️ GetchartMgzzsSSE 已被禁用,使用 HTTP 轮询方式(GetchartMgzzs)');
2344    return;
2345    
2346    console.log('🔍 GetchartMgzzsSSE 被调用, fundcode:', fundcode);
2347    
2348    // 🔥 防重复调用检查:如果正在连接相同的基金代码(1000ms内),直接返回
2349    var now = Date.now();
2350    if (sseConnectingFlag && 
2351        sseConnectingFlag.fundcode === fundcode && 
2352        (now - sseConnectingFlag.timestamp) < 1000) {
2353        console.log('⏭️ GetchartMgzzsSSE: 正在连接相同的基金代码,跳过本次请求', fundcode, '距离上次请求:', (now - sseConnectingFlag.timestamp) + 'ms');
2354        return;
2355    }
2356    
2357    // 检查必要的元素是否存在
2358    if (!$("#jzgs").length && !$("#gz-chart").length && !$("#gsvalue").length) {
2359        console.warn('GetchartMgzzsSSE: 图表容器元素不存在,延迟初始化');
2360        setTimeout(function() {
2361            GetchartMgzzsSSE(fundcode);
2362        }, 1000);
2363        return;
2364    }
2365    
2366    // 如果已有连接且是同一个基金代码,不需要重新连接
2367    if (valuationEventSource && valuationEventSource.readyState === EventSource.OPEN) {
2368        if (sseConnectingFlag && sseConnectingFlag.fundcode === fundcode) {
2369            console.log('✅ SSE 连接已存在且正常,跳过重复连接:', fundcode);
2370            return;
2371        }
2372    }
2373    
2374    // 如果已有连接,先关闭
2375    if (valuationEventSource) {
2376        console.log('🔍 关闭旧的 SSE 连接');
2377        valuationEventSource.close();
2378        valuationEventSource = null;
2379    }
2380    
2381    // 设置连接标志
2382    sseConnectingFlag = {
2383        fundcode: fundcode,
2384        timestamp: now
2385    };
2386    
2387    // 构建 SSE URL(使用 api.etf88.com 作为 API 前缀)
2388    var sseUrl = url + '/fundcode/getfundguzhizhangfuzoushistream?fundcode=' + fundcode;
2389    console.log('🔍 准备建立 SSE 连接:', sseUrl);
2390    
2391    // 创建 EventSource 连接
2392    try {
2393        valuationEventSource = new EventSource(sseUrl);
2394        console.log('🔍 EventSource 对象已创建');
2395    } catch (error) {
2396        console.error('❌ 创建 EventSource 失败:', error);
2397        return;
2398    }
2399    
2400    // 连接成功
2401    valuationEventSource.onopen = function(event) {
2402        console.log('✅ SSE 连接已建立:', sseUrl, 'readyState:', valuationEventSource.readyState);
2403        // 连接成功后,清除连接标志(允许后续重新连接)
2404        if (sseConnectingFlag && sseConnectingFlag.fundcode === fundcode) {
2405            sseConnectingFlag = null;
2406        }
2407    };
2408    
2409    // 接收数据
2410    valuationEventSource.onmessage = function(event) {
2411        console.log('📨 SSE 收到数据:', event.data ? event.data.substring(0, 100) + '...' : 'empty');
2412        try {
2413            var data = JSON.parse(event.data);
2414            
2415            if (data.type === 'connected') {
2416                console.log('SSE 连接确认');
2417                return;
2418            }
2419            
2420            if (data.type === 'error') {
2421                console.error('SSE 数据错误:', data.msg);
2422                resetkong();
2423                return;
2424            }
2425            
2426            if (data.type === 'data' && data.result) {
2427                // 更新净值估算信息(文本信息,不依赖图表容器)
2428                try {
2429                    if (data.latestPrice || data.latestChange || data.latestTime) {
2430                        updateEstimationInfo(data.latestPrice, data.latestChange, data.latestTime);
2431                    }
2432                } catch (error) {
2433                    console.error('SSE 更新净值估算信息失败:', error);
2434                }
2435                
2436                // 暂时禁用图表更新,避免影响页面渲染
2437                // TODO: 修复图表初始化问题后再启用
2438                /*
2439                // 然后更新图表(需要容器存在)
2440                if ($("#jzgs").length > 0 || $("#gz-chart").length > 0) {
2441                    // 延迟更新图表,确保 DOM 完全准备好
2442                    setTimeout(function() {
2443                        try {
2444                            updateValuationChart(data.result, data.latestPrice, data.latestChange, data.latestTime);
2445                        } catch (chartError) {
2446                            console.error('SSE 更新图表失败:', chartError);
2447                            // 即使图表更新失败,也不影响页面其他功能
2448                        }
2449                    }, 200);
2450                } else {
2451                    console.warn('SSE 数据已接收,但图表容器元素不存在,跳过图表更新');
2452                }
2453                */
2454            }
2455        } catch (error) {
2456            console.error('SSE 数据解析错误:', error);
2457        }
2458    };
2459    
2460    // 连接错误
2461    valuationEventSource.onerror = function(error) {
2462        console.error('❌ SSE 连接错误:', error, 'readyState:', valuationEventSource ? valuationEventSource.readyState : 'N/A');
2463        // 清除连接标志
2464        if (sseConnectingFlag && sseConnectingFlag.fundcode === fundcode) {
2465            sseConnectingFlag = null;
2466        }
2467        // 🔥 SSE 已禁用,不再重连
2468        // 连接断开后,延迟重连(5秒后)
2469        // setTimeout(function() {
2470        //     if (valuationEventSource && valuationEventSource.readyState === EventSource.CLOSED) {
2471        //         console.log('🔄 SSE 连接已断开,尝试重连...');
2472        //         // 清除连接标志,允许重连
2473        //         sseConnectingFlag = null;
2474        //         GetchartMgzzsSSE(fundcode);
2475        //     }
2476        // }, 5000);
2477    };
2478    
2479    // 页面卸载时关闭连接
2480    $(window).on('beforeunload', function() {
2481        if (valuationEventSource) {
2482            valuationEventSource.close();
2483            valuationEventSource = null;
2484        }
2485    });
2486}
2487
2488// 更新估值图表和净值估算信息
2489function updateValuationChart(dataArray, latestPrice, latestChange, latestTime) {
2490    try {
2491        if (!dataArray || !Array.isArray(dataArray) || dataArray.length === 0) {
2492            console.warn('updateValuationChart: 数据为空或不是数组');
2493            resetkong();
2494            return;
2495        }
2496        
2497        var gzTimeline = "ashare";
2498        var times = setgztime(null, { timeline: gzTimeline, points: dataArray }),
2499            filledUv = fillGuzhiAxisSeries(times, dataArray),
2500            y1 = filledUv.y1,
2501            y2 = filledUv.y2,
2502            chartseries = []
2503        
2504        // 安全地更新基金名称
2505        try {
2506            var fundName = $(".ti-left .name").text();
2507            if (fundName) {
2508                $("#gsfundname").text(fundName);
2509            }
2510        } catch (e) {
2511            // 忽略错误
2512        }
2513        
2514        // 处理数据,填充到时间序列
2515        $.each(times, function(tindex, k) {
2516            $.each(dataArray, function(index, i) {
2517                if (k == i.gztime) {
2518                    y1[tindex] = Number(i.gzzf)
2519                    y2[tindex] = Number(i.gzprice)
2520                }
2521            })
2522        })
2523        
2524        chartseries = [{
2525            name: '估算涨幅',
2526            color: "#FF0000",
2527            data: y1
2528        }]
2529        
2530        // 更新净值估算信息(价格、涨跌幅、时间)- 这部分不依赖图表容器
2531        try {
2532            updateEstimationInfo(latestPrice, latestChange, latestTime);
2533        } catch (e) {
2534            console.error('更新净值估算信息失败:', e);
2535        }
2536        
2537        // 🔥 暂时禁用 SSE 的图表更新,只更新文本信息,避免图表初始化问题导致页面崩溃
2538        // 图表更新由轮询方式(GetchartMgzzs)负责,SSE 只负责实时更新文本信息
2539        // 
2540        // 只更新 jzgs 的文本信息(不更新图表)
2541        if ($("#jzgs").length > 0) {
2542            try {
2543                $(".Chart-message span:eq(0)").text(getGuzhiDateText())
2544                $(".Chart-message span:eq(1)").text(latestTime || "--:--")
2545                $(".Chart-message span:eq(2)").attr("class", changecolor(latestChange)).text(latestPrice || "--")
2546                $(".Chart-message span:eq(3)").attr("class", changecolor(latestChange)).text((latestChange ? latestChange + '%' : '--'))
2547            } catch (e) {
2548                // 忽略文本更新错误
2549            }
2550        }
2551        
2552        // 只更新 gz-chart 的时间信息(不更新图表)
2553        if ($("#gz-chart").length > 0) {
2554            try {
2555                $("#gpontime").html(latestTime || "--:--")
2556            } catch (e) {
2557                // 忽略时间更新错误
2558            }
2559        }
2560    } catch (error) {
2561        console.error('updateValuationChart 函数执行错误:', error);
2562        // 确保错误不会影响页面其他功能
2563    }
2564}
2565
2566function extractEstimateDateText(raw) {
2567    var text = $.trim(raw || "");
2568    var match = text.match(/(\d{4}-\d{2}-\d{2})/);
2569    return match ? match[1] : "";
2570}
2571
2572function extractEstimateTimeText(raw) {
2573    var text = $.trim(raw || "");
2574    if (!text) {
2575        return "";
2576    }
2577
2578    var dateTimeMatch = text.match(/\d{4}-\d{2}-\d{2}\s+(\d{2}:\d{2})/);
2579    if (dateTimeMatch) {
2580        return dateTimeMatch[1];
2581    }
2582
2583    var timeMatch = text.match(/(\d{2}:\d{2})/);
2584    return timeMatch ? timeMatch[1] : "";
2585}
2586
2587function clampEstimateDisplayTime(timeText) {
2588    var normalizedTime = extractEstimateTimeText(timeText);
2589    if (!normalizedTime) {
2590        return "";
2591    }
2592
2593    var parts = normalizedTime.split(':').map(Number);
2594    if (parts.length !== 2 || isNaN(parts[0]) || isNaN(parts[1])) {
2595        return "";
2596    }
2597
2598    var totalMinutes = parts[0] * 60 + parts[1];
2599    return totalMinutes > 15 * 60 ? "15:00" : normalizedTime;
2600}
2601
2602function buildEstimateDateTimeText(dateText, timeText) {
2603    var finalDate = extractEstimateDateText(dateText) || extractEstimateDateText(timeText);
2604    var finalTime = clampEstimateDisplayTime(timeText);
2605
2606    if (finalDate && finalTime) {
2607        return finalDate + " " + finalTime;
2608    }
2609    if (finalDate) {
2610        return finalDate;
2611    }
2612    if (finalTime) {
2613        return finalTime;
2614    }
2615    return "---";
2616}
2617
2618function getGuzhiDateText(fallbackDate) {
2619    var dateText = $.trim(fallbackDate || "");
2620    if (/^\d{4}-\d{2}-\d{2}$/.test(dateText)) {
2621        return dateText;
2622    }
2623
2624    var currTimeText = $.trim($("#currTime").text() || "");
2625    var match = currTimeText.match(/\d{4}-\d{2}-\d{2}/);
2626    return match ? match[0] : "";
2627}
2628
2629// 更新净值估算信息(价格、涨跌幅、时间)并添加视觉反馈
2630function updateEstimationInfo(price, change, time, dateText) {
2631    var currentPrice = $.trim($("#gsvalue").text() || $("#fund-estimate").text() || "");
2632    var currentChange = $.trim(($("#gsTzf").text() || $("#fund-estimate-rzf").text() || "").replace('%', ''));
2633    var currentTime = $("#gstime").text();
2634    var currentDateTime = $.trim($("#currTime").text() || "");
2635
2636    var formattedPrice = price ? parseFloat(price).toFixed(4) : "--";
2637    var formattedChange = change ? parseFloat(change).toFixed(2) + '%' : "--";
2638    var formattedTime = clampEstimateDisplayTime(time) || "--:--";
2639    var effectiveDateText = extractEstimateDateText(dateText) || getGuzhiDateText(dateText);
2640    var formattedDateTime = buildEstimateDateTimeText(effectiveDateText, formattedTime);
2641
2642    if (formattedPrice !== currentPrice) {
2643        updateWithVisualFeedback("#gsvalue", formattedPrice);
2644        updateWithVisualFeedback("#fund-estimate", formattedPrice);
2645        updateWithVisualFeedback("#ZFgsvalue1", formattedPrice);
2646    } else {
2647        $("#gsvalue").text(formattedPrice);
2648        $("#fund-estimate").text(formattedPrice);
2649        $("#ZFgsvalue1").text(formattedPrice);
2650    }
2651
2652    var changeText = formattedChange;
2653    if (changeText !== currentChange + '%') {
2654        var $changeElement = $("#gsTzf");
2655        $changeElement.text(changeText);
2656        $changeElement.parents(".fund-d-value").removeClass("red green").addClass(changecolor(change || 0));
2657        updateWithVisualFeedback("#gsTzf", changeText);
2658
2659        var $fundEstimateChangeElement = $("#fund-estimate-rzf");
2660        $fundEstimateChangeElement.text(changeText);
2661        $fundEstimateChangeElement.closest(".fund-d-value").removeClass("red green").addClass(changecolor(change || 0));
2662        updateWithVisualFeedback("#fund-estimate-rzf", changeText);
2663
2664        var $zfChangeElement = $("#ZFgsvalue5");
2665        $zfChangeElement.text(changeText).removeClass("red green").addClass(changecolor(change || 0));
2666        updateWithVisualFeedback("#ZFgsvalue5", changeText);
2667    } else {
2668        $("#gsTzf").text(changeText).parents(".fund-d-value").removeClass("red green").addClass(changecolor(change || 0));
2669        $("#fund-estimate-rzf").text(changeText).closest(".fund-d-value").removeClass("red green").addClass(changecolor(change || 0));
2670        $("#ZFgsvalue5").text(changeText).removeClass("red green").addClass(changecolor(change || 0));
2671    }
2672
2673    if (formattedTime !== currentTime) {
2674        updateWithVisualFeedback("#gstime", formattedTime);
2675    } else {
2676        $("#gstime").text(formattedTime);
2677    }
2678
2679    if (formattedDateTime !== currentDateTime) {
2680        updateWithVisualFeedback("#currTime", formattedDateTime);
2681        updateWithVisualFeedback("#fund-estimate-date", formattedDateTime);
2682    } else {
2683        $("#currTime").text(formattedDateTime);
2684        $("#fund-estimate-date").text(formattedDateTime);
2685    }
2686
2687    if ($("#Gsjz").length > 0) {
2688        $("#Gsjz").text(formattedPrice).removeClass("red green").addClass(changecolor(change || 0));
2689    }
2690    if ($("#Gpert").length > 0) {
2691        var gpertText = change != null && change !== '' && !isNaN(parseFloat(change))
2692            ? ((parseFloat(change) >= 0 ? '+' : '') + parseFloat(change).toFixed(2) + '%')
2693            : formattedChange;
2694        $("#Gpert").text(gpertText).removeClass("red green").addClass(changecolor(change || 0));
2695    }
2696    if ($("#Gdate").length > 0 && effectiveDateText) {
2697        $("#Gdate").text(effectiveDateText);
2698    }
2699    if ($("#time").length > 0 && formattedTime && formattedTime !== "--:--") {
2700        $("#time").text(formattedTime);
2701    }
2702}
2703
2704// 带视觉反馈的更新函数(闪烁、高亮效果)
2705function updateWithVisualFeedback(selector, newValue) {
2706    var $element = $(selector);
2707    if ($element.length === 0) return;
2708    
2709    // 更新值
2710    $element.text(newValue);
2711    
2712    // 添加视觉反馈类(使用 CSS 类而不是直接修改样式,更易维护)
2713    $element.addClass('valuation-updated');
2714    
2715    // 1秒后移除视觉反馈类
2716    setTimeout(function() {
2717        $element.removeClass('valuation-updated');
2718    }, 1000);
2719}
2720
2721function isStandaloneRealtimeEstimatePage() {
2722    if ($("#fundxq[page-title-id='f_menu_jjgz']").length > 0) {
2723        return true;
2724    }
2725    return /\/jj\/[^\/]+\/jjgz\.shtml(?:$|\?)/.test(window.location.pathname + window.location.search);
2726}
2727
2728// 保留原函数,用于兼容(如果需要的话)
2729function isRealtimeEstimateTabActive() {
2730    var $realTimeTab = $(".hq-types li:contains('实时估值')");
2731    if ($realTimeTab.length > 0) {
2732        return $realTimeTab.hasClass('cur');
2733    }
2734    return isStandaloneRealtimeEstimatePage();
2735}
2736
2737function updateGuZhiFreshnessHint(payload) {
2738    var text = '';
2739    if (payload) {
2740        text = payload.freshness_hint || payload.gzfreshness_hint || '';
2741    }
2742    var $hint = $('#gz-freshness-hint');
2743    if (!$hint.length && $('.Chart-message').length > 0) {
2744        $hint = $('<p id="gz-freshness-hint" class="gz-freshness-hint"></p>');
2745        $('.Chart-message').first().after($hint);
2746    }
2747    if (!$hint.length) {
2748        return;
2749    }
2750    if (text) {
2751        $hint.text(text).css({ color: '#999', fontSize: '12px', margin: '6px 0 0' }).show();
2752    } else {
2753        $hint.hide().text('');
2754    }
2755}
2756
2757
2758function updateGuZhiCompareFields(payload) {
2759    var display = payload && (payload.display || null);
2760    if (!display) {
2761        display = payload || null;
2762    }
2763    if (!display) {
2764        return;
2765    }
2766    var f002 = display.f002 || '---';
2767    var sjzfText = display.sjzf_text || '---';
2768    var pldText = display.pld_text || '---';
2769    var sjzfNum = parseFloat(display.sjzf);
2770    if ($('#Gf002').length > 0) {
2771        $('#Gf002').text(f002);
2772    }
2773    if ($('#Gsjzf').length > 0) {
2774        $('#Gsjzf').text(sjzfText).removeClass('red green').addClass(changecolor(Number.isFinite(sjzfNum) ? sjzfNum : 0));
2775    }
2776    if ($('#Gpld').length > 0) {
2777        var $pld = $('#Gpld');
2778        $pld.text(pldText);
2779        $pld.removeClass('gz-pld-high gz-pld-lof-premium');
2780        if (display.pld_high) {
2781            $pld.addClass('gz-pld-high');
2782        } else if (display.lof_premium_high) {
2783            $pld.addClass('gz-pld-lof-premium');
2784        }
2785        if (display.pld_note) {
2786            $pld.attr('title', display.pld_note);
2787        } else {
2788            $pld.removeAttr('title');
2789        }
2790    }
2791    var methodHint = $.trim(display.method_hint || '');
2792    var $method = $('#guzhi-method-hint');
2793    if (!$method.length && $('.Chart-message--compare').length > 0) {
2794        $method = $('<p id="guzhi-method-hint" class="guzhi-method-hint"></p>');
2795        $('.Chart-message--compare').first().after($method);
2796    }
2797    if ($method.length) {
2798        if (methodHint) {
2799            $method.text(methodHint).css({ color: '#888', fontSize: '12px', margin: '6px 0 0' }).show();
2800        } else {
2801            $method.hide().text('');
2802        }
2803    }
2804    updateGuZhiFreshnessHint(display);
2805}
2806
2807function hasValidEstimateDateTimeText(text) {
2808    var normalized = $.trim(text || "");
2809    return /^\d{4}-\d{2}-\d{2}(\s+\d{2}:\d{2})?$/.test(normalized);
2810}
2811
2812// 非首页(无 index 内联 loadFundGuzhi)时,刷新顶部净值估算头区,与 SSR 口径对齐
2813function loadFundGuzhiHeader(fundcode) {
2814    if (!fundcode) {
2815        return;
2816    }
2817    Sendrequest('/fundcode/getfundguzhizhangfuzoushi?fundcode=' + fundcode, null, function success(data) {
2818        if (data && data.successcode == '1' && data.data && $.isArray(data.data.result) && data.data.result.length > 0) {
2819            var list = data.data.result;
2820            var latest = list[list.length - 1];
2821            updateEstimationInfo(latest.gzprice, latest.gzzf, latest.gztime, data.data.gzrq);
2822            updateGuZhiFreshnessHint(data.data);
2823            updateGuZhiCompareFields(data.data);
2824            return;
2825        }
2826        // 接口无数据时保留 SSR 已渲染值,不回写占位符
2827    }, function fail() {
2828        // 请求失败时保留 SSR 已渲染值
2829    });
2830}
2831
2832function resetEstimateHeaderPlaceholders() {
2833    if (!hasValidEstimateDateTimeText($("#currTime").text())) {
2834        $("#currTime").text("---");
2835    }
2836    if (!hasValidEstimateDateTimeText($("#fund-estimate-date").text())) {
2837        $("#fund-estimate-date").text("---");
2838    }
2839    $("#gstime").text("--:--");
2840    if ($("#gsvalue").length > 0 && $("#fund-estimate").length === 0) {
2841        $("#gsvalue").text("--");
2842        $("#gsvalue").parents(".hq-fund-data-item").hide();
2843    }
2844    $("#gsTzf").text("--");
2845}
2846
2847var guzhiChartReqSeq = 0;
2848// 最近一次可绘估值分时缓存:净值 tab 后台轮询也写入,切到「实时估值」时秒开
2849var guzhiChartPaintCache = null;
2850
2851function storeGuzhiChartPaintCache(fundcode, payload) {
2852    if (!fundcode || !payload) return;
2853    guzhiChartPaintCache = {
2854        fundcode: String(fundcode),
2855        times: payload.times || [],
2856        y1: payload.y1 || [],
2857        y2: payload.y2 || [],
2858        time: payload.time || '',
2859        fundGSjz: payload.fundGSjz,
2860        fundGSzf: payload.fundGSzf,
2861        gzrq: payload.gzrq || '',
2862        gz_timeline: payload.gz_timeline || 'ashare',
2863        updatedAt: Date.now()
2864    };
2865}
2866
2867function paintGuzhiChartFromCache(fundcode) {
2868    var cache = guzhiChartPaintCache;
2869    if (!cache || !fundcode || String(cache.fundcode) !== String(fundcode)) return false;
2870    if (!cache.times || !cache.times.length) return false;
2871    if ($("#jzgs").length === 0 && $("#gz-chart").length === 0) return false;
2872    try {
2873        if ($("#jzgs").length > 0) {
2874            highchartsG("jzgs", cache.times, cache.y1, cache.y2, cache.time, cache.fundGSjz, cache.fundGSzf, {
2875                gzrq: cache.gzrq,
2876                gz_timeline: cache.gz_timeline
2877            });
2878        }
2879        if ($("#gz-chart").length > 0 && $("#gz-chart").is(':visible')) {
2880            var chartseries = [{
2881                name: '估算涨幅',
2882                color: "#FF0000",
2883                data: cache.y1
2884            }];
2885            chartMgzzs("gz-chart", cache.times, chartseries, true);
2886        }
2887        return true;
2888    } catch (e) {
2889        console.warn('paintGuzhiChartFromCache failed', e);
2890        return false;
2891    }
2892}
2893
2894function GetchartMgzzs(fundcode) {
2895    console.log('🔍 GetchartMgzzs 被调用, fundcode:', fundcode);
2896    var times = [], y1 = [], y2 = [], chartseries = []
2897    $("#gsfundname").text($(".ti-left .name").text())
2898    
2899    var reqSeq = ++guzhiChartReqSeq;
2900    // 添加超时处理(仅当前请求仍是最新时才清空,避免 overlapping 轮询误杀)
2901    var timeoutId = setTimeout(function() {
2902        if (reqSeq !== guzhiChartReqSeq) return;
2903        console.warn('⚠️ GetchartMgzzs 请求超时,使用默认数据');
2904        resetkong();
2905    }, 10000); // 10秒超时
2906    
2907    Sendrequest('/fundcode/getfundguzhizhangfuzoushi?fundcode=' + fundcode, null, function success(data) {
2908        clearTimeout(timeoutId);
2909        if (reqSeq !== guzhiChartReqSeq) {
2910            console.log('⏭️ GetchartMgzzs: 过期响应已忽略, seq', reqSeq, guzhiChartReqSeq);
2911            return;
2912        }
2913        console.log('✅ GetchartMgzzs 请求成功:', data);
2914        if (data && data.successcode == '1' && data.data && data.data.result) {
2915            var list = data.data.result,
2916                time = ""
2917            
2918            console.log('📊 获取到估值数据,数量:', list.length);
2919            
2920            if (list.length === 0) {
2921                console.warn('⚠️ 估值数据为空');
2922                if (hasValidJjgzEstimateHeader()) {
2923                    showJjgzIntradayEmptyChart('暂无可绘制的估值分时(已保留最新估值)');
2924                } else {
2925                    resetkong();
2926                }
2927                return;
2928            }
2929            var gzTimeline = (data.data && data.data.gz_timeline) || "ashare"
2930            times = setgztime(null, { timeline: gzTimeline, points: list })
2931            var filled = fillGuzhiAxisSeries(times, list)
2932            y1 = filled.y1
2933            y2 = filled.y2
2934            time = filled.lastTime || ""
2935            chartseries = [{
2936                name: '估算涨幅',
2937                color: "#FF0000",
2938                data: y1
2939            }]
2940            var fundGSjz = list[list.length - 1]["gzprice"],
2941                fundGSzf = list[list.length - 1]["gzzf"]
2942            updateEstimationInfo(fundGSjz, fundGSzf, time, data.data.gzrq)
2943            updateGuZhiFreshnessHint(data.data)
2944            updateGuZhiCompareFields(data.data)
2945            storeGuzhiChartPaintCache(fundcode, {
2946                times: times,
2947                y1: y1,
2948                y2: y2,
2949                time: time,
2950                fundGSjz: fundGSjz,
2951                fundGSzf: fundGSzf,
2952                gzrq: data.data.gzrq,
2953                gz_timeline: gzTimeline
2954            })
2955            if ($("#gz-chart").length > 0) {
2956                var $gzChart = $("#gz-chart");
2957                // 🔥 延迟检查容器可见性,等待容器渲染完成
2958                var renderGzChart = function(attempt) {
2959                    attempt = attempt || 0;
2960                    var maxAttempts = 10; // 最多尝试10次
2961                    var delay = 200; // 每次延迟200ms
2962
2963                    if (!isRealtimeEstimateTabActive()) {
2964                        return;
2965                    }
2966                    
2967                    // 检查容器是否可见且有尺寸
2968                    if ($gzChart.is(':visible') && $gzChart.width() > 0 && $gzChart.height() > 0) {
2969                        try {
2970                            console.log('✅ gz-chart 容器已可见,开始渲染图表,尺寸:', $gzChart.width(), 'x', $gzChart.height());
2971                            chartMgzzs("gz-chart", times, chartseries, true)
2972                            $("#gpondate").html(data.data.gzrq)
2973                            $("#gpontime").html(time)
2974                        } catch (e) {
2975                            console.error('chartMgzzs 渲染失败:', e);
2976                        }
2977                    } else if (attempt < maxAttempts) {
2978                        // 容器不可见,延迟后重试
2979                        console.log('⏳ gz-chart 容器不可见或尺寸为0,延迟后重试 (尝试 ' + (attempt + 1) + '/' + maxAttempts + ')');
2980                        setTimeout(function() {
2981                            renderGzChart(attempt + 1);
2982                        }, delay);
2983                    } else {
2984                        console.warn('⚠️ gz-chart 容器在 ' + (maxAttempts * delay / 1000) + ' 秒后仍不可见或尺寸为0,跳过图表渲染');
2985                    }
2986                };
2987                
2988                // 立即尝试一次,如果失败则延迟重试
2989                renderGzChart(0);
2990            }
2991            if ($("#jzgs").length > 0) {
2992                var $jzgs = $("#jzgs");
2993                if ($("#Gsjz").length > 0) {
2994                    $("#Gdate").text(getGuzhiDateText(data.data.gzrq));
2995                    $("#time").text(time);
2996                    $("#Gsjz").attr("class", changecolor(fundGSzf)).text(fundGSjz);
2997                    $("#Gpert").attr("class", changecolor(fundGSzf)).text(fundGSzf + '%');
2998                } else {
2999                    $(".Chart-message span:eq(0)").text(getGuzhiDateText(data.data.gzrq));
3000                    $(".Chart-message span:eq(1)").text(time);
3001                    $(".Chart-message span:eq(2)").attr("class", changecolor(fundGSzf)).text(fundGSjz);
3002                    $(".Chart-message span:eq(3)").attr("class", changecolor(fundGSzf)).text(fundGSzf + '%');
3003                }
3004                
3005                // 🔥 延迟检查容器可见性,等待容器渲染完成
3006                var renderChart = function(attempt) {
3007                    attempt = attempt || 0;
3008                    var maxAttempts = 20; // 增加到20次,等待更长时间
3009                    var delay = 300; // 每次延迟300ms
3010                    
3011                    // 检查容器是否存在
3012                    if ($jzgs.length === 0) {
3013                        console.warn('⚠️ jzgs 容器不存在');
3014                        return;
3015                    }
3016                    
3017                    // 🔥 检查是否在"实时估值"标签页中(避免在其他标签页时继续检查)
3018                    var isRealTimeTabActive = isRealtimeEstimateTabActive();
3019                    if (!isRealTimeTabActive) {
3020                        return;
3021                    }
3022                    
3023                    // 检查容器是否可见且有尺寸
3024                    var isVisible = $jzgs.is(':visible');
3025                    var width = $jzgs.width();
3026                    var height = $jzgs.height();
3027                    // 🔥 使用 offsetHeight 或 clientHeight 作为备用检查(更准确)
3028                    var offsetHeight = $jzgs[0] ? $jzgs[0].offsetHeight : 0;
3029                    var clientHeight = $jzgs[0] ? $jzgs[0].clientHeight : 0;
3030                    var parentVisible = $jzgs.parents().filter(function() { return $(this).css('display') === 'none'; }).length === 0;
3031                    
3032                    // 🔥 允许高度为 0 的情况(Highcharts 会自动调整容器高度)
3033                    // 只要容器可见、有宽度,就可以渲染图表
3034                    if (isVisible && width > 0 && parentVisible) {
3035                        try {
3036                            console.log('✅ jzgs 容器已可见,开始渲染图表,尺寸:', width, 'x', height, '(offsetHeight:', offsetHeight, ', clientHeight:', clientHeight, ')');
3037                            highchartsG("jzgs", times, y1, y2, time, fundGSjz, fundGSzf, { gzrq: data.data.gzrq, gz_timeline: gzTimeline })
3038                        } catch (e) {
3039                            console.error('highchartsG 渲染失败:', e);
3040                        }
3041                    } else if (attempt < maxAttempts) {
3042                        // 容器不可见,延迟后重试
3043                        console.log('⏳ jzgs 容器状态检查 (尝试 ' + (attempt + 1) + '/' + maxAttempts + '):', {
3044                            isVisible: isVisible,
3045                            width: width,
3046                            height: height,
3047                            parentVisible: parentVisible,
3048                            display: $jzgs.css('display'),
3049                            visibility: $jzgs.css('visibility'),
3050                            isRealTimeTabActive: isRealTimeTabActive
3051                        });
3052                        setTimeout(function() {
3053                            renderChart(attempt + 1);
3054                        }, delay);
3055                    } else {
3056                        console.warn('⚠️ jzgs 容器在 ' + (maxAttempts * delay / 1000) + ' 秒后仍不可见或尺寸为0,跳过图表渲染');
3057                        console.warn('容器详细信息:', {
3058                            exists: $jzgs.length > 0,
3059                            isVisible: isVisible,
3060                            width: width,
3061                            height: height,
3062                            display: $jzgs.css('display'),
3063                            visibility: $jzgs.css('visibility'),
3064                            offset: $jzgs.offset(),
3065                            parent: $jzgs.parent().attr('class')
3066                        });
3067                    }
3068                };
3069                
3070                // 立即尝试一次,如果失败则延迟重试
3071                renderChart(0);
3072            }
3073
3074        } else {
3075            if (data && String(data.successcode) === '0') {
3076                console.log('ℹ️ GetchartMgzzs 暂无实时估值数据,使用占位展示');
3077                if (hasValidJjgzEstimateHeader()) {
3078                    showJjgzIntradayEmptyChart('暂无可绘制的估值分时(已保留最新估值)');
3079                    return;
3080                }
3081            } else {
3082                console.warn('⚠️ GetchartMgzzs 返回数据失败,successcode:', data ? data.successcode : 'N/A');
3083            }
3084            resetkong()
3085        }
3086    }
3086, function fail(xhr, status, error) {
3087        clearTimeout(timeoutId);
3088        if (reqSeq !== guzhiChartReqSeq) return;
3089        console.error('❌ GetchartMgzzs 请求失败:', status, error, 'HTTP状态码:', xhr ? xhr.status : 'N/A');
3090        resetkong()
3091    })
3092}
3093
3094function hasValidJjgzEstimateHeader() {
3095    var gsjzText = $.trim($("#Gsjz").text() || "");
3096    var gsjzNum = parseFloat(gsjzText);
3097    return gsjzText && gsjzText !== '---' && !isNaN(gsjzNum) && gsjzNum > 0;
3098}
3099
3100function showJjgzIntradayEmptyChart(message) {
3101    var text = message || '暂无可绘制的估值分时数据';
3102    if ($("#jzgs").length > 0) {
3103        try {
3104            if (window.FundCharts && typeof window.FundCharts.dispose === 'function') {
3105                window.FundCharts.dispose('jzgs');
3106            }
3107        } catch (eDispEmpty) {}
3108        $("#jzgs").html("<div style='padding:80px 0;text-align:center;color:#999;font-size:14px;'>" + text + "</div>");
3109    }
3110}
3111
3112function resetkong() {
3113    updateGuZhiFreshnessHint(null)
3114    resetEstimateHeaderPlaceholders()
3115    if ($("#gz-chart").length > 0) {
3116        $("#gz-chart").html("<p style='text-align:center;font-size:26px;color:#777;line-height:400px'>暂无数据</p>")
3117        $("#gpondate").html("0000-00-00")
3118        $("#gpontime").html("--:--")
3119        $("#ZFgsvalue1").text("--")
3120        $("#ZFgsvalue2").text("--")
3121        $("#ZFgsvalue3").text("--")
3122        $("#ZFgsvalue4").text("--")
3123        $("#ZFgsvalue5").text("--")
3124        $("#ZFgsvalue6").text("--")
3125        $("#ZFgsvalue7").text("--")
3126        $("#ZFgsvalue8").text("--")
3127    }
3128
3129    if ($("#jzgs").length > 0) {
3130        var preserveHeader = hasValidJjgzEstimateHeader();
3131        if ($("#Gsjz").length > 0 && !preserveHeader) {
3132            $("#Gdate").text("");
3133            $("#time").text("");
3134            $("#Gsjz").attr("class", "green").text("---");
3135            $("#Gpert").attr("class", "green").text("---");
3136        }
3137        $(".Chart-message span:eq(0)").text("")
3138        var $fdPzgz = $(".fd-pzgz")
3139        if ($fdPzgz.length > 0) {
3140            $fdPzgz.find("span:eq(0)").attr("class", "green").text("---")
3141            $fdPzgz.find("span:eq(1)").attr("class", "green").text("---")
3142            $fdPzgz.find("em").text("---")
3143        }
3144        if (!preserveHeader) {
3145            $(".Chart-message span:eq(0)").text("")
3146            $(".Chart-message span:eq(1)").text("")
3147            $(".Chart-message span:eq(2)").text("")
3148            $(".Chart-message span:eq(3)").text("")
3149        }
3150        try {
3151            if (isRealtimeEstimateTabActive() && $("#jzgs").is(':visible') && $("#jzgs").width() > 0 && !preserveHeader) {
3152                highchartsG("jzgs", setgztime(), [], [], "--:--", 0, 0)
3153            } else {
3154                showJjgzIntradayEmptyChart(preserveHeader ? '暂无可绘制的估值分时(已保留最新估值)' : '暂无数据');
3155            }
3156        } catch (e) {
3157            console.error('resetkong: highchartsG 调用失败:', e);
3158        }
3159    }
3160}
3161
3162function isContinuousGzTimeline(timeline) {
3163    var t = String(timeline || "").trim();
3164    return t === "spot24h" || t === "gold_session" || t === "us_session" || t === "hk_session" || t === "asia_session" || t === "eu_session";
3165}
3166
3167function goldSessionSortKey(t) {
3168    var p = String(t || "").trim().split(":");
3169    if (p.length !== 2) return 99999;
3170    var h = parseInt(p[0], 10);
3171    var mm = parseInt(p[1], 10);
3172    if (isNaN(h) || isNaN(mm)) return 99999;
3173    var mins = h * 60 + mm;
3174    return mins >= 360 ? mins : mins + 24 * 60;
3175}
3176
3177function sortGzTimesForTimeline(times, timeline) {
3178    var tl = String(timeline || "").trim();
3179    if (tl === "gold_session") {
3180        return (times || []).slice().sort(function (a, b) {
3181            return goldSessionSortKey(a) - goldSessionSortKey(b);
3182        });
3183    }
3184    return (times || []).slice().sort();
3185}
3186
3187function pad2Gz(n) {
3188    return n >= 10 ? String(n) : ("0" + n);
3189}
3190
3191function buildAshareGzTimes() {
3192    var s30 = 31, t60 = 60, a9 = [], a10 = [], a11 = [], b13 = [], b14 = [], b15 = ["15:00"];
3193    for (var k = 30; k < t60; k++) a9.push("09:" + pad2Gz(k));
3194    for (var m = 0; m < t60; m++) {
3195        var c = pad2Gz(m);
3196        a10.push("10:" + c);
3197        b13.push("13:" + c);
3198        b14.push("14:" + c);
3199    }
3200    for (var i = 0; i < s30; i++) a11.push("11:" + pad2Gz(i));
3201    return a9.concat(a10, a11, b13, b14, b15);
3202}
3203
3204function buildSessionWindowGzTimes(timeline, stepMin) {
3205    stepMin = stepMin || 5;
3206    var ranges = [];
3207    if (timeline === "us_session") {
3208        ranges = [[0, 4 * 60], [21 * 60 + 30, 24 * 60]];
3209    } else if (timeline === "hk_session" || timeline === "asia_session") {
3210        ranges = [[9 * 60 + 30, 12 * 60], [13 * 60, 16 * 60 + 10]];
3211    } else if (timeline === "eu_session") {
3212        ranges = [[15 * 60, 23 * 60 + 30]];
3213    } else if (timeline === "gold_session") {
3214        ranges = [[6 * 60, 24 * 60], [0, 6 * 60]];
3215    } else {
3216        ranges = [[0, 24 * 60]];
3217    }
3218    var times = [];
3219    for (var r = 0; r < ranges.length; r++) {
3220        var start = ranges[r][0], end = ranges[r][1];
3221        for (var m = start; m < end; m += stepMin) {
3222            var hh = Math.floor(m / 60) % 24;
3223            var mm = m % 60;
3224            times.push(pad2Gz(hh) + ":" + pad2Gz(mm));
3225        }
3226    }
3227    return times;
3228}
3229
3230function mergePointGzTimes(baseTimes, points, timeline) {
3231    var seen = {};
3232    var out = [];
3233    function add(t) {
3234        t = String(t || "").trim();
3235        if (!t || seen[t]) return;
3236        seen[t] = 1;
3237        out.push(t);
3238    }
3239    for (var i = 0; i < (baseTimes || []).length; i++) add(baseTimes[i]);
3240    for (var j = 0; j < (points || []).length; j++) {
3241        var p = points[j] || {};
3242        add(p.gztime);
3243    }
3244    return sortGzTimesForTimeline(out, timeline);
3245}
3246
3247function setgztime(type, opts) {
3248    opts = opts || {};
3249    var timeline = String(opts.timeline || "ashare").trim() || "ashare";
3250    var gztime;
3251    if (isContinuousGzTimeline(timeline)) {
3252        gztime = mergePointGzTimes(buildSessionWindowGzTimes(timeline, opts.stepMin || 5), opts.points || [], timeline);
3253        if ((!opts.points || opts.points.length === 0) && gztime.length === 0) {
3254            gztime = buildAshareGzTimes();
3255        }
3256    } else {
3257        gztime = buildAshareGzTimes();
3258    }
3259    if (type) {
3260        var defaultData = [];
3261        for (var h = 0; h < gztime.length; h++) defaultData.push(null);
3262        return defaultData;
3263    }
3264    return gztime;
3265}
3266
3267function fillGuzhiAxisSeries(times, list) {
3268    var y1 = [];
3269    var y2 = [];
3270    var lastTime = "";
3271    for (var i = 0; i < times.length; i++) {
3272        y1.push(null);
3273        y2.push(null);
3274    }
3275    var map = {};
3276    for (var j = 0; j < (list || []).length; j++) {
3277        var item = list[j] || {};
3278        if (!item.gztime) continue;
3279        map[item.gztime] = item;
3280        lastTime = item.gztime;
3281    }
3282    for (var t = 0; t < times.length; t++) {
3283        var hit = map[times[t]];
3284        if (!hit) continue;
3285        if (hit.gzzf !== undefined && hit.gzzf !== null && hit.gzzf !== "") y1[t] = Number(hit.gzzf);
3286        if (hit.gzprice !== undefined && hit.gzprice !== null && hit.gzprice !== "" && hit.gzprice !== "---") y2[t] = Number(hit.gzprice);
3287    }
3288    return { y1: y1, y2: y2, lastTime: lastTime };
3289}
3290
3291function highchartsG(chartId, xdata, chartdata, y2, lastTime, lasty1, lasty2, opts) {
3292    if (window.FundCharts) {
3293        var o = opts || {};
3294        FundCharts.renderGuzhiIntraday(chartId, xdata, chartdata, y2, lastTime, lasty1, lasty2, {
3295            height: o.height || 400,
3296            gzrq: o.gzrq || '',
3297            gz_timeline: o.gz_timeline || ''
3298        });
3299    }
3300}
3301
3302function chartMgzzs(chartId, xdata, chartseries, legend) {
3303    if (window.FundCharts) {
3304        FundCharts.renderGuzhiTrend(chartId, xdata, chartseries, legend);
3305    }
3306}
3307
3308function FormatUtcDate(obj) {
3309    return moment.utc(obj, 'YYYY-MM-DD').valueOf();
3310}
3311
3312function loadHbLineChart(targetId, subTitle, lineData, navigator, rangeSelector, dwjz, ljjz, fundcode) {
3313    if (window.FundCharts) {
3314        var meta = fundcode ? { fundcode: fundcode } : null;
3315        FundCharts.renderNavStockChart(targetId, subTitle, lineData, navigator, rangeSelector, dwjz, ljjz, meta);
3316    }
3317}
3318
3319function loadHbnhsyLineChart(targetId, title, lineData, fundcode) {
3320    if (window.FundCharts) {
3321        var meta = fundcode ? { fundcode: fundcode } : null;
3322        FundCharts.renderHbWfsyChart(targetId, title, lineData, meta);
3323    }
3324}
3325
3326function loadColumnChart(targetId, titleHtml, columnData, legend, unit) {
3327    if (window.FundCharts) {
3328        FundCharts.renderColumnChart(targetId, titleHtml, columnData, legend, unit);
3329    }
3330}
3331
3332function loadSameCatogaryFundsHb(_target, title, sameCatogaryData) {
3333    if (window.FundCharts) {
3334        FundCharts.renderAreaChart(_target, sameCatogaryData);
3335    }
3336}
3337
3338function loadColumn2Y(targetId, titleHtml, columnData, legend, unit, unit2) {
3339    if (window.FundCharts) {
3340        FundCharts.renderColumn2Y(targetId, titleHtml, columnData, legend, unit, unit2);
3341    }
3342}
3343
3344function loadline(_target, title, sameCatogaryData) {
3345    if (!window.FundCharts || !sameCatogaryData || !sameCatogaryData.length) return;
3346    var categories = [];
3347    var first = sameCatogaryData[0].data || [];
3348    first.forEach(function (pt) {
3349        if (Array.isArray(pt)) {
3350            var d = new Date(pt[0]);
3351            categories.push(d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2));
3352        }
3353    });
3354    var series = sameCatogaryData.map(function (s) {
3355        return {
3356            name: s.name,
3357            color: s.color,
3358            data: (s.data || []).map(function (pt) {
3359                return Array.isArray(pt) ? pt[1] : (pt.y != null ? pt.y : pt);
3360            })
3361        };
3362    });
3363    FundCharts.renderMobileNavChart(title, _target, categories, series);
3364}
3365
3366function loadbarChart(_target, data, options) {
3367    if (window.FundCharts) {
3368        FundCharts.renderPieChart(_target, data, options || {});
3369    }
3370}
3371
3372function loadspiderChart(_target, data) {
3373    if (window.FundCharts) {
3374        FundCharts.renderRadarChart(_target, data);
3375    }
3376}
3377$(function() {
3378    for (var h = 0; h < $(".showdet").length; h++) {
3379        var getdetal = $(".showdet").eq(h).attr("data-detail")
3380        if (getdetal && getdetal.length > 220) {
3381            $(".unfold").eq(h).show()
3382        }
3383    }
3384    $(".unfold").click(function() {
3385        var txt = $(this).siblings("span.showdet").attr("data-detail")
3386        $(this).hide()
3387        $(this).siblings("span.fold").show()
3388        $(this).siblings("span.showdet").text(txt)
3389    })
3390    $(".fold").click(function() {
3391        var txt = $(this).siblings("span.showdet").attr("data-detail")
3392        $(this).hide()
3393        $(this).siblings("span.unfold").show()
3394        $(this).siblings("span.showdet").text(txt.substring(0, 220))
3395    })
3396    if ($(".qusmes").length > 0) {
3397        $(".question").mouseover(function() {
3398            $(this).next(".quesbox").show();
3399        }).mouseout(function() {
3400            $(this).next(".quesbox").hide();
3401        });
3402    }
3403})
3404
3405function Mx_loadMore(obj) {
3406    $(obj).parent(".show2table").find("table").toggle();
3407    var txt = $(obj).html()
3408    if (txt == "显示全部持仓明细&gt;&gt;") {
3409        $(obj).html("收起持仓明细&gt;&gt;")
3410    } else {
3411        $(obj).html("显示全部持仓明细&gt;&gt;")
3412    }
3413
3414}
3415
3416function S_MX_year(selectedIndex) {
3417    var MX_year_item = $(".year_tabs_box .year_tab_item");
3418    MX_year_item.eq(selectedIndex).show().siblings().hide();
3419    try {
3420        var year = '';
3421        var sel = document.getElementById('jjcc');
3422        if (sel && sel.options && sel.options.length) {
3423            var idx = (typeof selectedIndex === 'number' && selectedIndex >= 0) ? selectedIndex : sel.selectedIndex;
3424            if (sel.options[idx]) {
3425                year = String(sel.options[idx].value || '').trim();
3426            }
3427        }
3428        if (!year && sel) {
3429            year = String(sel.value || '').trim();
3430        }
3431        var mapEl = document.getElementById('zcg-mx-holdings-obs-map');
3432        var obsEl = document.getElementById('zcg-mx-holdings-obs');
3433        if (!mapEl || !obsEl) {
3434            return;
3435        }
3436        var map = {};
3437        try {
3438            map = JSON.parse(mapEl.textContent || '{}') || {};
3439        } catch (e) {
3440            map = {};
3441        }
3442        var text = map[year] || '';
3443        var textEl = obsEl.querySelector('.zcg-mx-holdings-obs-text');
3444        if (text) {
3445            if (textEl) {
3446                textEl.textContent = text;
3447            } else {
3448                obsEl.innerHTML = '<strong>持仓观察:</strong><span class="zcg-mx-holdings-obs-text"></span>';
3449                textEl = obsEl.querySelector('.zcg-mx-holdings-obs-text');
3450                if (textEl) textEl.textContent = text;
3451                var codeMatch = (window.location.pathname || '').match(/\/jj\/([0-9A-Za-z]{6})\//);
3452                if (codeMatch) {
3453                    var a = document.createElement('a');
3454                    a.href = '/jj/' + codeMatch[1].toUpperCase() + '/ldcp.shtml';
3455                    a.textContent = '基金评级 >';
3456                    obsEl.appendChild(a);
3457                }
3458            }
3459            obsEl.setAttribute('data-year', year);
3460            obsEl.style.display = '';
3461        } else {
3462            obsEl.style.display = 'none';
3463        }
3464    } catch (err) {
3465        /* ignore obs sync errors */
3466    }
3467}
3468function hasLoginStateFromPage() {
3469    if ((getCookie('username') || '').trim()) return true;
3470    if ((getCookie('uid') || '').trim()) return true;
3471    var domName = $('.islogin .username:visible').first().text().trim();
3472    return !!(domName && domName !== '用户');
3473}
3474
3475$(function() {
3476    // 合格投资者认定弹窗:仅对 /jj/ 基金详情页(私募/集合理财)且未登录用户生效
3477    var path = window.location.pathname || '';
3478    var isFundDetailPage = /^\/jj\/[0-9a-zA-Z]+\/?$/.test(path) || /^\/jj\/[0-9a-zA-Z]+\/(index\.shtml)?$/.test(path);
3479    if (!isFundDetailPage) return;
3480
3481    var tagText = $('.tag_c').text() || '';
3482    var isPrivateOrQs = tagText.indexOf('私募') !== -1 || tagText.indexOf('集合理财') !== -1;
3483    if (!isPrivateOrQs) return;
3484
3485    // 已登录(cookie/页面状态)直接不弹
3486    if (hasLoginStateFromPage()) return;
3487
3488    // 再用接口兜底校验一次登录态,避免 cookie 未及时写入导致误弹
3489    Sendrequest('/user/islogin?t=' + Date.parse(new Date()), null, function success(data) {
3490        if (data && data.successcode == '1') {
3491            if (data.uid) setCookie('uid', data.uid);
3492            if (data.username) setCookie('username', data.username);
3493            return;
3494        }
3495        showmaskL();
3496    }, function fail() {
3497        showmaskL();
3498    });
3499})
3500
3501function showmaskL() {
3502    if ($('.darkmask').length > 0) {
3503        return;
3504    }
3505    let innerHTML = '<div class="darkmask"><div class="dkm_warp"><div class="dkm_header"><div><span class="logo_22"></span><span class="tit7">合格投资者认定</span></div></div><div class="dkm_content"><div class="wxts">温馨提示</div><div><p>易天富基金谨遵《私募投资基金募集行为管理办法》之规定,只向特定对象宣传推介私募基金。</p><p>若您有意进行私募投资基金投资,请确认您符合法律法规所规定的投资者标准:</p><p>合格投资者标准:本人/本机构承诺具备相应风险识别能力和风险承担能力,投资于单只产品的金额符合产品合同约定的起投金额的个人/单位:</p><p style="color: #ff8400;">(1)具有2年以上投资经历,且满足以下条件之一:家庭金融净资产不低于300万元,家庭金融资产不低于500万元,或者近3年本人年均收入不低于50万元。(2)最近1年末净资产不低于1000万元的法人单位。(3)金融管理部门视为合格投资者的其他情形。</p></div><p class="pcheckok"><input type="checkbox" id="ch_ok" checked="checked" onchange="dms_checkok(this)"><label>我已阅读如上合格投资者认定条款</label></p><div><button id="viweb" class="b1" onclick="dms_login()">登录查看</button><button class="b2" onclick="gohome()">返回首页</button></div></div></div></div>';
3506    $("body").append(innerHTML);
3507
3508}
3509
3510function dms_checkok(cb) {
3511    if (cb.checked) {
3512        $("#viweb").addClass("b1").removeClass("b3")
3513    } else {
3514        $("#viweb").addClass("b3").removeClass("b1")
3515    }
3516}
3517
3518function dms_login() {
3519    var ischecked = $("#ch_ok").is(":checked")
3520    if (!ischecked) {
3521        return false
3522    }
3523    setCookie("referrer", true)
3524    location.href = "https://www.etf88.com/login_new.html"
3525}
3526
3527function gohome() {
3528    location.href = "https://www.etf88.com/"
3529}
3530var gmbdChartData = [{
3531    name: "规模",
3532    data: [
3533        [1370131200000, 0.7695],
3534        [1370217600000, 0.0648],
3535        [1370304000000, 0.7645],
3536        [1370390400000, 0.7638],
3537        [1370476800000, 0.7549],
3538        [1370563200000, 0.7562],
3539        [1370736000000, 0.7574],
3540        [1370822400000, 0.0543],
3541        [1370908800000, 0.751],
3542        [1370995200000, 0.7498],
3543        [1371081600000, 0.7477],
3544    ]
3545}]
3546
3547function areamap(_target, Ydata) {
3548    if (window.FundCharts) {
3549        FundCharts.renderAreaChart(_target, Ydata);
3550    }
3551}
3552$(function() {
3553    $(".main-link").click(function() {
3554        var _rf = $(this)
3555        _rf.siblings(".sub-link").slideToggle("500")
3556        _rf.find(".icon-down").toggleClass('close')
3557
3558        console.log(arro, "dddd")
3559
3560    })
3561    if ($("#gmbdChart").length > 0) {
3562        areamap("gmbdChart", gmbdChartData)
3563    }
3564    $(".ytfjj-tags li").each(function(index, i) {
3565        var _ts = $(this)
3566        _ts.click(function() {
3567            _ts.addClass("active").siblings().removeClass("active")
3568            areamap("gmbdChart", gmbdChartData)
3569        })
3570    })
3571    $(".letters li").each(function(index, i) {
3572        var _ls = $(this)
3573        _ls.click(function() {
3574            $(".letters_tabcontent ul[data_id=" + _ls.text() + "]").show().siblings().hide()
3575            _ls.addClass("on").siblings().removeClass("on")
3576        })
3577    })
3578    $("._a_1 .tabs_menus_nav_item").each(function(index, i) {
3579        var _kT = $(this)
3580        _kT.click(function() {
3581            console.log("ffff")
3582            _kT.parents("._a_1").siblings(".c_tabs_content_box").find(".c_tabs_item_content:eq(" + index + ")").show().siblings().hide()
3583            _kT.addClass("active").siblings().removeClass("active")
3584        })
3585
3586    })
3587})
3588
3589function zschanrt(type, obj) {
3590    var sl = $(".ti-left .code").text()
3591    var fundcode = sl.slice(1, sl.length - 1)
3592    $(obj).addClass("active").siblings().removeClass("active")
3593    
3594    // 🔥 优化:添加防重复调用检查
3595    var currentKey = fundcode + '_' + (type || 'default');
3596    var now = Date.now();
3597    
3598    // 如果正在加载相同的图表(1000ms内),直接返回
3599    if (chartLoadingFlag && 
3600        chartLoadingFlag.key === currentKey && 
3601        (now - chartLoadingFlag.timestamp) < 1000) {
3602        console.log('zschanrt 防重复调用:正在加载相同的图表,跳过本次请求', currentKey);
3603        return;
3604    }
3605
3606    if (window.FundCharts && FundCharts.applyNavFromCache && FundCharts.getNavCacheEntry(fundcode)) {
3607        if (FundCharts.applyNavFromCache(fundcode, type)) {
3608            chartLoadingFlag = null
3609            return
3610        }
3611    }
3612    
3613    chartsJZZS(fundcode, type)
3614}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.