Deprecated: Creation of dynamic property Typecho\Widget\Request::$feed is deprecated in /www/wwwroot/blog.iletter.top/var/Widget/Archive.php on line 253
白荼日记 - 前端 2025-07-27T02:32:00+08:00 Typecho https://blog.iletter.top/index.php/feed/atom/tag/%E5%89%8D%E7%AB%AF/ <![CDATA[typecho给后台admin界面添加图标分析]]> https://blog.iletter.top/index.php/archives/406.html 2025-07-27T02:32:00+08:00 2025-07-27T02:32:00+08:00 DelLevin https://blog.iletter.top 觉得后台admin空荡荡的,索性加个图表自己魔改一下下。以下是完整的index.php

微信图片_20250727022229.png

<?php
include 'common.php';
include 'header.php';
include 'menu.php';

$stat = \Widget\Stat::alloc();
?>
<div class="main">
    <div class="container typecho-dashboard">
        <?php include 'page-title.php'; ?>
        <div class="row typecho-page-main">
            <div class="col-mb-12 welcome-board" role="main">
                <p><?php _e('目前有 <em>%s</em> 篇文章, 并有 <em>%s</em> 条关于你的评论在 <em>%s</em> 个分类中.',
                        $stat->myPublishedPostsNum, $stat->myPublishedCommentsNum, $stat->categoriesNum); ?>
                    <!--<br><?php _e('点击下面的链接快速开始:'); ?></p>-->

                <ul id="start-link" class="clearfix">
                    <?php if ($user->pass('contributor', true)): ?>
                        <li><a href="<?php $options->adminUrl('write-post.php'); ?>"><?php _e('撰写新文章'); ?></a></li>
                        <?php if ($user->pass('editor', true) && 'on' == $request->get('__typecho_all_comments') && $stat->waitingCommentsNum > 0): ?>
                            <li>
                                <a href="<?php $options->adminUrl('manage-comments.php?status=waiting'); ?>"><?php _e('待审核的评论'); ?></a>
                                <span class="balloon"><?php $stat->waitingCommentsNum(); ?></span>
                            </li>
                        <?php elseif ($stat->myWaitingCommentsNum > 0): ?>
                            <li>
                                <a href="<?php $options->adminUrl('manage-comments.php?status=waiting'); ?>"><?php _e('待审核评论'); ?></a>
                                <span class="balloon"><?php $stat->myWaitingCommentsNum(); ?></span>
                            </li>
                        <?php endif; ?>
                        <?php if ($user->pass('editor', true) && 'on' == $request->get('__typecho_all_comments') && $stat->spamCommentsNum > 0): ?>
                            <li>
                                <a href="<?php $options->adminUrl('manage-comments.php?status=spam'); ?>"><?php _e('垃圾评论'); ?></a>
                                <span class="balloon"><?php $stat->spamCommentsNum(); ?></span>
                            </li>
                        <?php elseif ($stat->mySpamCommentsNum > 0): ?>
                            <li>
                                <a href="<?php $options->adminUrl('manage-comments.php?status=spam'); ?>"><?php _e('垃圾评论'); ?></a>
                                <span class="balloon"><?php $stat->mySpamCommentsNum(); ?></span>
                            </li>
                        <?php endif; ?>
                        <?php if ($user->pass('administrator', true)): ?>
                            <li><a href="<?php $options->adminUrl('manage-posts.php'); ?>"><?php _e('文章管理'); ?></a></li>
                            <!--<li><a href="<?php $options->adminUrl('themes.php'); ?>"><?php _e('更换外观'); ?></a></li>-->
                            <li><a href="<?php $options->adminUrl('plugins.php'); ?>"><?php _e('插件管理'); ?></a></li>
                            <li><a href="<?php $options->adminUrl('options-general.php'); ?>"><?php _e('系统设置'); ?></a>
                            </li>
                        <?php endif; ?>
                    <?php endif; ?>
                    <!--<li><a href="<?php $options->adminUrl('profile.php'); ?>"><?php _e('更新我的资料'); ?></a></li>-->
                </ul>
            </div>
            

            

            <div class="col-mb-12 col-tb-4" role="complementary">
                <section class="latest-link">
                    <h3><?php _e('最近发布的文章'); ?></h3>
                    <?php \Widget\Contents\Post\Recent::alloc('pageSize=10')->to($posts); ?>
                    <ul>
                        <?php if ($posts->have()): ?>
                            <?php while ($posts->next()): ?>
                                <li>
                                    <span><?php $posts->date('n.j'); ?></span>
                                    <a href="<?php $posts->permalink(); ?>" class="title"><?php $posts->title(); ?></a>
                                </li>
                            <?php endwhile; ?>
                        <?php else: ?>
                            <li><em><?php _e('暂时没有文章'); ?></em></li>
                        <?php endif; ?>
                    </ul>
                </section>
            </div>

            <div class="col-mb-12 col-tb-4" role="complementary">
                <section class="latest-link">
                    <h3><?php _e('最近得到的回复'); ?></h3>
                    <ul>
                        <?php \Widget\Comments\Recent::alloc('pageSize=10')->to($comments); ?>
                        <?php if ($comments->have()): ?>
                            <?php while ($comments->next()): ?>
                                <li>
                                    <span><?php $comments->date('n.j'); ?></span>
                                    <a href="<?php $comments->permalink(); ?>"
                                       class="title"><?php $comments->author(false); ?></a>:
                                    <?php $comments->excerpt(35, '...'); ?>
                                </li>
                            <?php endwhile; ?>
                        <?php else: ?>
                            <li><?php _e('暂时没有回复'); ?></li>
                        <?php endif; ?>
                    </ul>
                </section>
            </div>
            

            <!--<div class="col-mb-12 col-tb-4" role="complementary">-->
            <!--    <section class="latest-link">-->
            <!--        <h3><?php _e('官方最新日志'); ?></h3>-->
            <!--        <div id="typecho-message">-->
            <!--            <ul>-->
            <!--                <li><?php _e('读取中...'); ?></li>-->
            <!--            </ul>-->
            <!--        </div>-->
            <!--    </section>-->
            <!--</div>-->

                        <!-- 图表显示区域 -->
            <div class="col-mb-12 welcome-board">
                <h3 style="color:black"><?php _e('文章统计数据'); ?></h3>
                <div style="display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 20px;">
                    <!-- 月度发布图表 -->
                    <div style="flex: 1; min-width: 400px;">
                        <canvas id="monthlyPostChart" height="300"></canvas>
                    </div>
                    <!-- 状态分布图表 -->
                    <div style="flex: 1; min-width: 300px;">
                        <canvas id="statusChart" height="300"></canvas>
                    </div>
                </div>
                <div style="display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 20px;">
                    <!-- 分类文章统计 -->
                    <div style="flex: 1; min-width: 400px;">
                        <canvas id="categoryChart" height="300"></canvas>
                    </div>
                    <!-- 标签文章统计 -->
                    <div style="flex: 1; min-width: 400px;">
                        <canvas id="tagChart" height="300"></canvas>
                    </div>
                </div>
            </div>
                               
            
            
        </div>
    </div>
</div>

<?php
include 'copyright.php';
include 'common-js.php';
?>

<script src="https://blog.iletter.top/usr/blog_img/chart.js"></script>
<script>
  $(document).ready(function() {
    <?php
    // ==================== PHP 数据计算部分 ====================
    
    // 1. 月度文章统计数据
    $db = Typecho_Db::get();
    
    // 月度文章统计
    $select = $db->select()
        ->from('table.contents')
        ->where('type = ?', 'post')
        ->where('status = ?', 'publish');
    $posts = $db->fetchAll($select);
    
    $monthlyData = [];
    $monthLabels = [];
    $currentYear = date('Y');
    $currentMonth = date('n');
    
    // 初始化最近12个月的数据
    for ($i = 11; $i >= 0; $i--) {
        $year = $currentYear;
        $month = $currentMonth - $i;
        if ($month <= 0) {
            $month += 12;
            $year--;
        }
        $key = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT);
        $monthlyData[$key] = 0;
        $monthLabels[$key] = $year . '年' . $month . '月';
    }
    
    // 统计实际数据
    foreach ($posts as $post) {
        $created = $post['created'];
        $year = date('Y', $created);
        $month = date('m', $created);
        $key = $year . '-' . $month;
        
        if (isset($monthlyData[$key])) {
            $monthlyData[$key]++;
        }
    }
    
    // 2. 文章状态统计
    $published = $db->fetchObject($db->select(['COUNT(*)' => 'count'])
        ->from('table.contents')
        ->where('type = ?', 'post')
        ->where('status = ?', 'publish'))->count;
        
    $draft = $db->fetchObject($db->select(['COUNT(*)' => 'count'])
        ->from('table.contents')
        ->where('type = ?', 'post_draft')
        ->where('status = ?', 'publish'))->count;
        
        
    // 3. 分类文章统计
    $categories = $db->fetchAll($db->select()->from('table.metas')->where('type = ?', 'category'));
    $categoryData = [];
    $categoryLabels = [];
    
    foreach ($categories as $category) {
        $count = $db->fetchObject($db->select(['COUNT(*)' => 'count'])
            ->from('table.relationships')
            ->where('mid = ?', $category['mid']))->count;
        
        if ($count > 0) { // 只显示有文章的分类
            $categoryLabels[] = $category['name'];
            $categoryData[] = (int)$count;
        }
    }
    
    // 4. 标签文章统计 (取前10个)
    $tags = $db->fetchAll($db->select()->from('table.metas')->where('type = ?', 'tag'));
    $tagData = [];
    $tagLabels = [];
    
    foreach ($tags as $tag) {
        $count = $db->fetchObject($db->select(['COUNT(*)' => 'count'])
            ->from('table.relationships')
            ->where('mid = ?', $tag['mid']))->count;
        
        if ($count > 0) {
            $tagLabels[] = $tag['name'];
            $tagData[] = (int)$count;
        }
    }
    
    // 按数量排序,取前10个
    array_multisort($tagData, SORT_DESC, $tagLabels);
    $topTagLabels = array_slice($tagLabels, 0, 10);
    $topTagData = array_slice($tagData, 0, 10);
    
    // 准备图表数据
    $chartMonthlyData = [
        'labels' => array_values($monthLabels),
        'values' => array_values($monthlyData)
    ];
    
    $chartStatusData = [
        'published' => (int)$published,
        'draft' => (int)$draft
    ];
    
    $chartCategoryData = [
        'labels' => $categoryLabels,
        'values' => $categoryData
    ];
    
    $chartTagData = [
        'labels' => $topTagLabels,
        'values' => $topTagData
    ];
    ?>
    
    // ==================== JavaScript 图表渲染部分 ====================
    
    // 1. 月度文章图表
    var monthlyData = <?php echo json_encode($chartMonthlyData); ?>;
    if (monthlyData.labels && monthlyData.labels.length > 0) {
        var ctx1 = document.getElementById('monthlyPostChart').getContext('2d');
        new Chart(ctx1, {
            type: 'line',
            data: {
                labels: monthlyData.labels,
                datasets: [{
                    label: '每月发文数',
                    data: monthlyData.values,
                    borderColor: 'rgb(75, 192, 192)',
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    tension: 0.1,
                    fill: true
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            precision: 0
                        }
                    }
                },
                plugins: {
                    title: {
                        display: true,
                        text: '月度文章发布趋势'
                    }
                }
            }
        });
    }
    
    // 2. 文章状态图表
    var statusData = <?php echo json_encode($chartStatusData); ?>;
    if (statusData) {
        var ctx2 = document.getElementById('statusChart').getContext('2d');
        new Chart(ctx2, {
            type: 'doughnut',
            data: {
                labels: ['已发布', '草稿'],
                datasets: [{
                    data: [statusData.published, statusData.draft],
                    backgroundColor: [
                        'rgba(75, 192, 192, 0.8)',
                        'rgba(255, 205, 86, 0.8)',
                    ],
                    borderWidth: 1
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                    title: {
                        display: true,
                        text: '文章状态分布'
                    },
                    legend: {
                        position: 'bottom'
                    }
                }
            }
        });
    }
    
    // 3. 分类文章图表
    var categoryData = <?php echo json_encode($chartCategoryData); ?>;
    if (categoryData.labels && categoryData.labels.length > 0) {
        var ctx3 = document.getElementById('categoryChart').getContext('2d');
        new Chart(ctx3, {
            type: 'bar',
            data: {
                labels: categoryData.labels,
                datasets: [{
                    label: '文章数量',
                    data: categoryData.values,
                    backgroundColor: 'rgba(153, 102, 255, 0.8)',
                    borderColor: 'rgba(153, 102, 255, 1)',
                    borderWidth: 1
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            precision: 0
                        }
                    }
                },
                plugins: {
                    title: {
                        display: true,
                        text: '各分类文章数量'
                    }
                }
            }
        });
    }
    
    // 4. 标签文章图表
    var tagData = <?php echo json_encode($chartTagData); ?>;
    if (tagData.labels && tagData.labels.length > 0) {
        var ctx4 = document.getElementById('tagChart').getContext('2d');
        new Chart(ctx4, {
            type: 'bar',
            data: {
                labels: tagData.labels,
                datasets: [{
                    label: '文章数量',
                    data: tagData.values,
                    backgroundColor: 'rgba(255, 159, 64, 0.8)',
                    borderColor: 'rgba(255, 159, 64, 1)',
                    borderWidth: 1
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    y: {
                        beginAtZero: true,
                        ticks: {
                            precision: 0
                        }
                    }
                },
                plugins: {
                    title: {
                        display: true,
                        text: '热门标签文章数量 (前10)'
                    }
                }
            }
        });
    }
});
</script>

<script>
$(document).ready(function () {
        var ul = $('#typecho-message ul'), cache = window.sessionStorage,
            html = cache ? cache.getItem('feed') : '',
            update = cache ? cache.getItem('update') : '';

        if (!!html) {
            ul.html(html);
        } else {
            html = '';
            $.get('<?php $options->index('/action/ajax?do=feed'); ?>', function (o) {
                for (var i = 0; i < o.length; i++) {
                    var item = o[i];
                    html += '<li><span>' + item.date + '</span> <a href="' + item.link + '" target="_blank">' + item.title
                        + '</a></li>';
                }

                ul.html(html);
                cache.setItem('feed', html);
            }, 'json');
        }

        function applyUpdate(update) {
            if (update.available) {
                $('<div class="update-check message error"><p>'
                    + '<?php _e('您当前使用的版本是 %s'); ?>'.replace('%s', update.current) + '<br />'
                    + '<strong><a href="' + update.link + '" target="_blank">'
                    + '<?php _e('官方最新版本是 %s'); ?>'.replace('%s', update.latest) + '</a></strong></p></div>')
                    .insertAfter('.typecho-page-title').effect('highlight');
            }
        }

        if (!!update) {
            applyUpdate($.parseJSON(update));
        } else {
            $.get('<?php $options->index('/action/ajax?do=checkVersion'); ?>', function (o, status, resp) {
                applyUpdate(o);
                cache.setItem('update', resp.responseText);
            }, 'json');
        }
    });


              

</script>
<?php include 'footer.php'; ?>

其他插件推荐:
[post cid="405" /]

]]>
<![CDATA[去除宝塔界面一些烦人的东西]]> https://blog.iletter.top/index.php/archives/400.html 2025-07-25T13:18:02+08:00 2025-07-25T13:18:02+08:00 DelLevin https://blog.iletter.top 更新之后越来越难用了,自己懒得配置nginx,mysql,这些环境,开心版的又怕后门,自己改后端的js文件看的脑瓜子疼,反正都是界面,直接js删掉一些dom元素就行了。有需要的可以拿走。

// ==UserScript==
// @name         宝塔面板优化 - 界面美化
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  移除指定 class 元素、替换特定 span 文字内容,并删除包含“需求反馈”的 span
// @match        http://152.136.153.72:9999/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    console.log('⚠ 脚本准备加载');

    // 定义需要移除的 class 组合
    const selectorsToRemove = [
        '.h-auto.mt-\\[1\\.2rem\\]',                         // h-auto mt-[1.2rem]
        '.svgtofont-desired.\\!mr-4px.\\!text-\\[16px\\]',   // svgtofont-desired !mr-4px !text-[16px]
        '.icon-end-time-free.icon-unpaid-ltd',                // icon-end-time-free icon-unpaid-ltd
        '.el-card.is-always-shadow.is-always-shadow.\\!border-\\[\\#efefef\\]',  // el-card is-always-shadow is-always-shadow !border-[#efefef]
        '.icon-end-time.icon-unpaid-ltd' // icon-end-time icon-unpaid-ltd
    ];

    // 移除指定 class 的元素
    const removeElementsByClass = () => {
        selectorsToRemove.forEach(selector => {
            document.querySelectorAll(selector).forEach(el => el.remove());
        });
    };

    // 替换指定 span 的文字内容
    const replaceSpanText = () => {
        const spanSelector = 'span.mr-1rem.ml-\\[-1\\.5rem\\]';
        document.querySelectorAll(spanSelector).forEach(span => {
            span.textContent = '精简版';
        });
    };

    // 删除 span/button 包含 指定字符 的 span 元素
    const removeSpansWithText = () => {
        const spans = document.querySelectorAll('span');
        spans.forEach(span => {
            if (span.textContent.trim() == '需求反馈') {
                span.remove();
            }
        });
        const buttons = document.querySelectorAll('button');
        buttons.forEach(span => {
            if (span.textContent.trim() == '立即体验') {
                span.remove();
            }
        });
    };
    // 删除指定class 包含置顶字符的元素
    const removeClassWithText =()=>{
        const parents = document.querySelectorAll('.header-child-tab');
        parents.forEach(span => {
            if (
                span.textContent.trim() == '安全检测' ||
                span.textContent.trim() == '违规词检测' ||
                span.textContent.trim() == 'PHP网站安全' ||
                span.textContent.trim() == '入侵防御' ||
                span.textContent.trim() == '系统加固' ||
                span.textContent.trim() == '扫描感知' ||
                span.textContent.trim() == '日志审计' ||
                span.textContent.trim() == 'SSH登录日志'
               ) {
                span.remove();
            }
        });
        const classparents = document.querySelectorAll('.el-collapse-item');
        classparents.forEach(span => {
            if (
                span.textContent.trim().includes('top5')
               ) {
                span.remove();
            }
        });
    }

    // 删除包含特定子元素的 el-col 元素
    const removeParentIfChildExists = () => {
        // 获取所有 el-col.el-col-6.wrapper-item 元素
        const parents = document.querySelectorAll('.el-col.el-col-6.wrapper-item');
        parents.forEach(parent => {
            // 检查子元素是否包含指定的 class
            const child = parent.querySelector('.absolute.-left-1\\.5.-top-1');
            if (child) {
                parent.remove(); // 移除父元素
            }
        });
    };


    // 综合执行函数
    const removeElements = () => {
        removeElementsByClass();
        replaceSpanText();
        removeSpansWithText();
        removeParentIfChildExists();
        removeClassWithText();
    };
    // 等待dom加载完毕后执行综合函数
    let timeoutId;
    const debouncedRemove = () => {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(removeElements, 200);
    };
    let runCount = 0;
    const maxRuns = 5;
    const observer = new MutationObserver((mutations, obs) => {
        if (runCount >= maxRuns) {
            obs.disconnect();
            return;
        }
        obs.disconnect();
        debouncedRemove();
        runCount++;
        obs.observe(document.body, { childList: true, subtree: true });
    });
    observer.observe(document.body, { childList: true, subtree: true });
    // 点击一次执行一次综合函数
    let clickTimeout;
    document.addEventListener('click', () => {
        clearTimeout(clickTimeout);
        clickTimeout = setTimeout(removeElements, 200);
    });
    // 初始执行(延迟 1 秒,等待 DOM 加载)
    setTimeout(() => {
        removeElements();
    }, 1000);
})();
]]>
<![CDATA[nvm命令自查(node版本管理)]]> https://blog.iletter.top/index.php/archives/324.html 2024-11-10T14:22:19+08:00 2024-11-10T14:22:19+08:00 DelLevin https://blog.iletter.top nvm 命令命令解释nvm --help展示帮助nvm --version已安装的nvm版本nvm install version下载对应的 node 版本(version)mvn install --reinstall-packages-from=重新安装对应的 node 版本nvm install --lts仅从LTS版本中选择安装nvm install --lts=仅从特定LTS系列的版本中选择nvm install --skip-default-packages跳过默认软件包文件nvm install --latest-npm安装后,在给定的节点版本上升级到最新的npmnvm install --no-progress没有下载进度条nvm uninstall卸载对应的 node 版本nvm uninstall --lts卸载LTS版本nvm uninstall --lts=卸载指定的LTS 版本nvm use使用对应的 node 版本nvm use --lts使用LTS 版本nvm use --lts=使用指定的LTS 版本nvm list展示安装的 node 版本(可以简写为nvm ls)nvm current显示当前节点的激活版本nvm version展示当前的 node 版本nvm exec [--silent] []使用指定的 版本运行 command命令nvm run [--silent] []使用指定的版本运行 argsnvm alias对 version 版本设置一个别名nvm unalias删除这个别名nvm install-latest-npm在当前node 版本上升级最新的npm版本nvm reinstall-packages将version版本中的全局安装包安装到当前版本中nvm unload从shell中卸载nvmnvm on开启node版本管理nvm off关闭node版本管理nvm node_mirror [url]设置node镜像nvm npm_mirror [url]设置npm 镜像 ]]> <![CDATA[typecho的handsome主题微调]]> https://blog.iletter.top/index.php/archives/322.html 2024-11-06T16:48:00+08:00 2024-11-06T16:48:00+08:00 DelLevin https://blog.iletter.top 用国typecho和wordpress之后,个人还是比较中意typecho这个,handsome主题相对全面,所以就选择使用它了。但是总有一些自己小瑕疵需要自己去调整,比如归档页面的文章目录等等,handsome版本: 9.2.1(个人是php菜鸡,没学过,一边百度一边改。)

取消归档页面的文章目录

找到主题文件下面的sidebar.php然后打开编辑,搜索到<!--非文章页面-->这个之后,然后会看到<?php echo PostContent::returnTOC($this->is('page'),false) ?>这个代码,这个就是非文章界面的文章目录显示。

<!--非文章页面-->
      <?php if (!($this->is('post'))) : ?>
      <section id="tag_cloud" class="widget widget_tag_cloud wrapper-md padder-v-none clear">
       <h5 class="widget-title m-t-none"><?php _me("标签云") ?></h5>
          <div class="tags l-h-2x panel wrapper-sm padder-v-ssm">
              <?php Typecho_Widget::widget('Widget_Metas_Tag_Cloud','ignoreZeroCount=1&limit=30')->to($tags); ?>
              <?php if($tags->have()): ?>
                  <?php while ($tags->next()): ?>
                      <a href="<?php $tags->permalink();?>" class="label badge"><?php $tags->name(); ?></a>
                  <?php endwhile; ?>
              <?php endif; ?>
          </div>
      </section>

        <!--<?php echo PostContent::returnTOC($this->is('page'),false) ?>-->
        <?php 
            // 获取当前页面的路径
            $currentUrlPath = parse_url($this->permalink, PHP_URL_PATH);
            // 判断是否为页面,且 URL 中不包含 "archive"
            if ($this->is('page') && substr($currentUrlPath, -12) !== 'archive.html' ) {
                echo PostContent::returnTOC(true, false);
            }
        ?>
          
      
      <?php else: ?>

如果你想对具体方法进行操作,可以找到libs/content/PostContent.php 这个就是相应的右侧边栏的一些用到的方法。

去除掉友情链接的内页链接

友情链接嘛,就是links,那我们就在主题文件夹下搜索links

<ul class="nav no-padder b-b">
                    <li class="nav-item active"><a class="nav-link" href data-toggle="tab" data-target="#my-info"><?php
                            _me("申请友链")
                            ?></a></li>
                    <!--<li class="nav-item"><a class="nav-link" href data-toggle="tab" data-target="#tab_2"><?php _me("内页链接") ?></a></li>-->
                    <li class="nav-item"><a class="nav-link" href data-toggle="tab" data-target="#tab_4"><?php _me("全站链接") ?></a></li>
                    <li class="nav-item"><a class="nav-link" href data-toggle="tab" data-target="#tab_3"><?php _me("推荐链接") ?></a></li>
                </ul>

这里就是对链接的显示,注释掉你不喜欢的就可以了。

注释完毕后,如果你有强迫症的话,可以想我一样进行接下来的更改。针对后台管理的友情链接的操作。找到插件里面的handsome插件,找到Plugin.php这个文件,里面有个form函数,就是他,你若找到的话可以看到该函数下面有一部分的代码。注释掉你不喜欢的就可以了。

$sort = new Typecho_Widget_Helper_Form_Element_Select('sort', array(
            'ten' => '全站链接,首页左侧边栏显示',
            // 'one' => '内页链接,在独立页面中显示(需要新建独立页面<a href="https://handsome2.ihewro.com/#/plugin" target="_blank">友情链接</a>)',
            'good' => '推荐链接,在独立页面中显示',
            'others' => '失效链接,不置输出,标注暂时失效的友链'
        ), 'ten', _t('链接输出位置*'), '选择友情链接输出的位置');

左侧边栏导航 配置

打开主题的后台,可以参考本博客的配置进行修改,左侧边栏的图标用的是feather图标,点击链接跳转查看,https://feathericons.com/)

[
    {
        "name": "云盘",
        "feather": "cloud",
        "link": "https://alist.ittoolman.com/",
        "target": "_blank"
    },
    {
        "name": "图床",
        "feather": "inbox",
        "link": "https://img.ittoolman.com/",
        "target": "_blank"
    },
    {
        "name": "相册",
        "feather": "image",
        "link": "https://blog.iletter.top/index.php/category/image/",
        "target": "_self"
    },
    {
        "name": "分类",
        "feather": "layout",
        "sub": [
            {
                "name": "随笔",
                "feather": "edit",
                "target": "_self",
                "link": "https://blog.iletter.top/index.php/category/sui-bi/"
            },
            {
                "name": "技术笔记",
                "feather": "monitor",
                "target": "_self",
                "link": "https://blog.iletter.top/index.php/category/ji-shu-bi-ji/"
            },
            {
                "name": "文章收藏",
                "feather": "scissors",
                "target": "_self",
                "link": "https://blog.iletter.top/index.php/category/wen-zhang/"
            }
        ]
    },
    {
        "name": "归档",
        "feather": "archive",
        "link": "https://blog.iletter.top/index.php/archive.html",
        "target": "_self"
    },
    {
        "name": "时光机",
        "feather": "clock",
        "link": "https://blog.iletter.top/index.php/cross.html",
        "target": "_self"
    },
    {
        "name": "友人帐",
        "feather": "users",
        "link": "https://blog.iletter.top/index.php/links.html",
        "target": "_self"
    },
    {
        "name": "关于我",
        "feather": "coffee",
        "link": "https://blog.iletter.top/index.php/start-page.html",
        "target": "_self"
    }
]
]]>
<![CDATA[uni-app微信小程序属性为0]]> https://blog.iletter.top/index.php/archives/162.html 2024-06-04T23:52:00+08:00 2024-06-04T23:52:00+08:00 DelLevin https://blog.iletter.top 前几日在封装一个组件的时候,接收数据的时候遇到一个属性等于0,但是,前端死活不显示

这里是有值的,但

到了前端就没有了,卧槽,太神奇了是不是。打印一下看看

打印出来传入值竟然是undefined而不是0,一定是闹鬼了。。。

最后再不断定位错误的时候,发现是这里的毛病

post: item.post || '', // 添加 post 属性,默认值为空字符串

原因是当数值为0的时候自己就默认是false

0在布尔上下文中被认为是假值,因此 item.post||‘’ 会在 item.post 为 0 时返回空字符串。所以说为了保留0,需要改成

 item.post !== undefined ? item.post : ''

来确保仅在 item.postundefined 时返回空字符串。

下面贴一下源代码

<template>
    <view>
        <!-- 绑定个人信息 -->
        <u-popup :show="localBindCompShow" @close="onClickOverlayClose" @open="localBindCompShow =true" mode="center"
            round="20" :closeOnClickOverlay="true">
            <view class="bindCompCard">

                <view class="header-container">
                    <view class='header-button'>
                        <u-button type="info" size="5" icon="close" @click="onClickOverlayClose" :plain="true"
                            :hairline="true"></u-button>
                    </view>

                    <view class="header-text">绑定企业信息</view>
                    <u-tabs :list="bindCompTabList" @click="selectTab" class="tabs"></u-tabs>
                </view>

                <view v-if="!bindCompNewShow">
                    <view class="supplier-container">
                        <view v-for="(item, index) in itemsToDisplay" :key="index" class="supplier-card">
                            <view class="supplier-details">
                                <view class="company-name">{{ item.companyName }}</view>
                                <view class="supplier-name">政采账号:{{ item.supplierAccessName }}</view>
                            </view>
                            <view class="bind-button">

                                <view v-if="!item.isShow">
                                    <view class="post-select">
                                        {{ getPostText(item.post) }}
                                    </view>
                                    <button size="default" type="default"
                                        style="margin-top: 20rpx;color:#ffffff;background-color:#189f33;border-color:#ffffff;border-radius: 40rpx;height: 60rpx;display: flex;align-items: center;justify-content: center;">已绑定</button>
                                </view>


                                <view v-if="item.isShow">
                                    <view class="post-select" v-if="item.isShow">
                                        <uni-data-select v-model="item.post" :localdata="postList"
                                            placeholder="选择职位"></uni-data-select>
                                    </view>
                                    <button size="default" type="default" @click="submitBindCompCard(item)"
                                        style="margin-top: 20rpx;color:#ffffff;background-color:#4874cb;border-color:#ffffff;border-radius: 40rpx;height: 60rpx;display: flex;align-items: center;justify-content: center;">提交绑定</button>
                                </view>
                            </view>
                        </view>
                    </view>
                </view>

                <view v-if="bindCompNewShow">
                    <u--form labelPosition="left" :model="formBindComp">
                        <u-form-item label="政采网账号" prop="formBindComp.supplierAccessName" borderBottom labelWidth='200'>
                            <u--input v-model="formBindComp.supplierAccessName" border="none"></u--input>
                        </u-form-item>
                        <u-form-item label="职位" prop="formBindComp.post" borderBottom labelWidth='200'>
                            <uni-data-select v-model="formBindComp.post" :localdata="postList"
                                placeholder="选择职位"></uni-data-select>
                        </u-form-item>
                    </u--form>

                    <!-- <u-button type="success">提交</u-button> -->
                    <button size="default" type="default" @click="submitBindComp"
                        style="margin-top: 40rpx;color:#ffffff;background-color:#4874cb;border-color:#ffffff;border-radius: 40rpx;height: 60rpx;display: flex;align-items: center;justify-content: center;">提交绑定</button>
                </view>

            </view>
        </u-popup>
    </view>
</template>

<script>
    import {
        mapState,
        mapMutations
    } from 'vuex';
    import {
        getFieldList,
        postBindEnterprise,
        getUserBindComp,
        getUserHaveBindComp,
    } from "@/api/user.js";
    export default {
        name: 'BindComp',
        props: {
            bindCompShow: {
                type: Boolean,
                default: true
            },
            userId: {
                type: String,
            },
            itemList: {
                type: Array,
                default: () => []
            }
        },
        computed: {
            ...mapState("user", ["token", "tokenExpire", "userInfo"]),
            localBindCompShow: {
                get() {
                    return this.bindCompShow;
                },
                set(val) {
                    this.$emit('update:bindCompShow', val)
                }
            },
            itemsToDisplay() {
                let list = this.itemList.length > 0 ? this.itemList : this.supplierList;
                // 处理 list,添加 post 和 isShow 属性
                let processedList = list.map(item => ({
                    ...item,
                    post: item.post !== undefined ? item.post : '', // 添加 post 属性,保留原值或默认值为空字符串
                    isShow: item.isShow !== undefined ? item.isShow : true // 添加 isShow 属性,默认值为 true
                }));
                return processedList;        
            }
        },
        data() {
            return {
                supplierList: [],
                bindCompNewShow: false,
                bindCompTabList: [{
                        name: '政采企业信息绑定',
                    },
                    {
                        name: '绑定其他企业',
                    }
                ],
                postList: [],
                formBindComp: {
                    supplierAccessName: '',
                    post: '',
                    userId: '',
                }
            };
        },
        mounted() {
            this.fieldListGet();
            if (this.userInfo.id != undefined) {
                this.userBindCompGet();
            }
        },
        created() {
            
        },
        methods: {
            ...mapMutations("user", ["setUserInfo"]),
            async submitBindComp() {
                this.formBindComp.userId = this.userId
                if (!this.formBindComp.userId) {
                    this.formBindComp.userId = this.userInfo.id
                }

                if (this.formBindComp.userId == "") {
                    uni.$u.toast('绑定失败,请尝试重新登陆后绑定!');
                    return;
                }
                if (this.formBindComp.supplierAccessName === "") {
                    uni.$u.toast('政采网账号不能为空!');
                    return;
                }
                if (this.formBindComp.post === "") {
                    uni.$u.toast('职位信息不能为空!');
                    return;
                }
                const {
                    code,
                    msg,
                    data
                } = await postBindEnterprise(this.formBindComp);

                if (code === 0) {
                    // console.log(data)
                    uni.$u.toast('绑定企业成功!');
                    setTimeout(() => {
                        this.$emit('close-popup');
                        uni.reLaunch({
                            url: '/pages/index/index'
                        });
                    }, 1000);
                }

            },
            async submitBindCompCard(item) {
                if (item.post === undefined || item.post === "") {
                    uni.$u.toast('企业职位不能为空!');
                    return;
                }
                let userID = this.userId;
                if (!userID) {
                    userID = this.userInfo.id
                }

                const params = {
                    supplierAccessName: item.supplierAccessName,
                    post: item.post,
                    userId: userID,
                }
                console.log(params)
                const {
                    code,
                    msg,
                    data
                } = await postBindEnterprise(params);
                if (code === 0) {
                    // console.log(data)
                    // 更新 item 的 isShow 属性
                    this.$set(item, 'isShow', false);
                    uni.$u.toast('绑定' + item.companyName + '成功!');
                    // 更新绑定企业
                    this.userBindCompGet();

                }
            },
            onClickOverlayClose() {
                this.$emit('close-popup');
            },
            //获取用户下面的政采企业信息
            async userBindCompGet() {
                try {
                    // 如果resBindComp里面包含resHaveBind的数据,那么就设置该企业已经绑定好了
                    const resHaveBind = await getUserHaveBindComp();
                    const resBindComp = await getUserBindComp();
                    if (resHaveBind.code === 0 && resBindComp.code === 0) {

                        const haveBindMap = new Map();
                        resHaveBind.data.forEach(item => {
                            haveBindMap.set(item.supplierId, item.post);
                        });

                        this.supplierList = resBindComp.data.map(item => {
                            if (haveBindMap.has(item.supplierId)) {
                                return {
                                    ...item,
                                    post: haveBindMap.get(item.supplierId),
                                    isShow: false
                                };
                            }
                            return item;
                        });

                        console.log(this.supplierList);
                    }
                } catch (error) {
                    console.error('获取用户绑定企业信息失败', error);
                }

            },
            //获取职位信息
            async fieldListGet() {
                const {
                    code,
                    msg,
                    data
                } = await getFieldList({
                    fieldName: "register_post"
                });
                if (code === 0 && data) {
                    // console.log(data)
                    this.postList = data.map(post => {
                        return {
                            value: post.dictCode,
                            text: post.dictName
                        }
                    });
                    // console.log(this.postList)
                }
            },
            getPostText(postValue) {
                const postItem = this.postList.find(post => post.value === postValue);
                // console.log(postValue, postItem)
                return postItem ? postItem.text : '未知职位';
            },
            selectTab(item) {
                // console.log(item.name)
                if (item.name == "绑定其他企业") {
                    this.bindCompNewShow = true;
                } else {
                    this.bindCompNewShow = false;
                }
            },
        },
    }
</script>

<style scoped lang="less">
    .bindCompCard {
        width: 650rpx;
        padding: 20rpx;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        text-align: center;
    }

    .header-container {
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 100%;
        margin-bottom: 20rpx;
    }

    .header-button {
        position: absolute;
        width: 80rpx;
        top: 0;
        right: 5px;
        margin: 10rpx;
    }

    .header-text {
        font-size: 36rpx;
        margin-bottom: 10rpx;
        text-align: center;
        width: 100%;
    }

    .tabs {
        width: 100%;
    }

    // ------------------------
    .supplier-container {
        display: flex;
        flex-direction: column;
        gap: 20rpx;
        padding: 20rpx;
        height: 600rpx;
        overflow: auto;
    }

    .supplier-card {
        background: #fff;
        border-radius: 10rpx;
        box-shadow: 0 2rpx 5rpx rgba(0, 0, 0, 0.1);
        padding: 20rpx;
        display: flex;
        // flex-direction: column;
        gap: 10rpx;
    }

    .supplier-details {
        margin-top: 27rpx;
        width: 360rpx;
        display: flex;
        flex-direction: column;
        gap: 10rpx;
    }

    .company-name,
    .supplier-name {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        width: 100%;
    }

    .company-name {
        font-size: 32rpx;
        font-weight: bold;
        color: #333;
    }

    .supplier-name {
        font-size: 32rpx;
        color: #666;
    }

    .post-select {
        margin-top: 10rpx;
        width: 222rpx;
    }

    .bind-button {
        margin-left: 10rpx;
    }
</style>
]]>
<![CDATA[前端实现div的高度拖拽改变]]> https://blog.iletter.top/index.php/archives/153.html 2023-11-03T23:49:00+08:00 2023-11-03T23:49:00+08:00 DelLevin https://blog.iletter.top 实现代码

html

        <div class="contactPerson_bottom_div" id="fuDiv">
            <div class="dragDiv" @mousedown="dragEagle" >
                    <el-icon><SemiSelect /></el-icon>
            </div>
         </div>

script

dragEagle(e) {
            var targetDiv = document.getElementById('fuDiv');
            //得到点击时该地图容器的宽高:
            var targetDivHeight = targetDiv.offsetHeight;
            var startX = e.clientX;
            var startY = e.clientY;
            document.onmousemove = function (e) {
                e.preventDefault();
                //得到鼠标拖动的宽高距离:取绝对值
                var distY = Math.abs(e.clientY - startY);

                //往上方拖动:
                if (e.clientY < startY) {
                    targetDiv.style.height = (targetDivHeight + distY) + 'px';
                }

                //往下方拖动:
                if (e.clientX < startX && e.clientY > startY) {
                    targetDiv.style.height = targetDivHeight - distY + 'px';
                }

                if (parseInt(targetDiv.style.height) >= 800) {
                    targetDiv.style.height = 800 + 'px';
                }
                if (parseInt(targetDiv.style.height) <= 320) {
                    targetDiv.style.height = 320 + 'px';
                }
            }
            document.onmouseup = function () {
                document.onmousemove = null;
            }
        },

css

.contactPerson_bottom_div {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    border-top-style: solid;
    border-color: black;
    background: rgb(255, 255, 255);
    color: rgb(0, 0, 0);
    z-index: 99;
    height: 320px;
    overflow-y: auto;/* 允许垂直滚动 */
}
.dragDiv{
     height:10px;
    display: flex; 
    align-items: center; 
    justify-content: center;   
}
.dragDiv:hover {
    /* background-color: #666; */
    cursor: pointer;/* 在hover的时候,鼠标指针变成手 */
}

首先针对需要设置的div添加一个id,再通过getElementById获取到目标div。

然后在内部添加一个div,可以设置成自己想要的形式,做明显的标记使用。针对这个div设置操作活动@mousedown。也就是当鼠标按下去的时候调用此dragEagle方法,在此之前一定要传入对象参数e。

var targetDivHeight = targetDiv.offsetHeight;: 这行代码获取了目标 div 元素的当前高度,并将其存储在 targetDivHeight 变量中。

var startX = e.clientX;var startY = e.clientY;: 这两行代码分别记录了鼠标点击位置的横坐标(X坐标)和纵坐标(Y坐标),用于后续计算鼠标拖动的距离。

document.onmousemove = function (e) { ... }: 这是一个鼠标移动事件处理程序,当用户按住鼠标左键并移动时,会触发这个事件处理程序。在这个处理程序中,计算了鼠标拖动的距离,并根据拖动的方向来改变目标 div 元素的高度。

  • e.preventDefault();: 这一行代码用于防止浏览器默认的拖拽行为,确保我们自己的逻辑生效。
  • 通过比较鼠标当前的 e.clientY 和初始点击时的 startY 来计算垂直方向上的拖动距离 distY
  • 接下来的条件判断分别处理向上拖动和向下拖动的情况。如果鼠标向上拖动,就增加目标 div 的高度,如果鼠标向下拖动,就减小目标 div 的高度。这样可以实现调整高度的效果。
  • 最后的两个条件判断用于限制目标 div 的最小和最大高度,以避免高度超出一定范围

document.onmouseup = function () { ... }: 这是鼠标释放事件处理程序,在鼠标左键释放时触发。它用于清除鼠标移动事件处理程序,即当鼠标释放后停止拖动。

在这之中

                if (parseInt(targetDiv.style.height) >= 800) {
                    targetDiv.style.height = 800 + 'px';
                }
                if (parseInt(targetDiv.style.height) <= 320) {
                    targetDiv.style.height = 320 + 'px';
                }

这一段是限制div拖动的最大高度和最小高度

]]>
<![CDATA[vue3无关系组件之间的方法调用]]> https://blog.iletter.top/index.php/archives/146.html 2023-07-30T23:48:00+08:00 2023-07-30T23:48:00+08:00 DelLevin https://blog.iletter.top 要实现在第一个页面中的方法 onClick 调用第二个页面中的 updateBankToUser 方法,我们可以使用事件总线来实现跨组件通信。在Vue 3中,我们可以使用 mitt 库来创建事件总线。

首先,安装 mitt 库:

npm install mitt

然后,我们可以创建一个全局的事件总线,并在第二个页面中将 updateBankToUser 方法注册为一个事件处理程序,然后在第一个页面的 onClick 方法中触发这个事件,从而调用第二个页面中的方法。

以下是示例代码:

创建全局事件总线 bus.ts:

// bus.ts
import mitt from 'mitt';

export const bus = mitt();

第二个页面 user.vue:

<template>
  <div>
    <!-- ...其他代码... -->
    <el-button @click="updateBankToUser">更新银行的用户</el-button>
  </div>
</template>

<script setup>
import { bus } from './bus';

const updateBankToUser = async () => {
  console.log('更新银行的用户');
  // 在这里执行您的业务逻辑
}
bus.on('callUpdateBankToUser', updateBankToUser);
</script>

第一个页面 parent.vue:

<template>
  <div>
    <el-button @click="onClick">点击调用第二个页面的方法</el-button>
  </div>
</template>

<script setup>
import { bus } from './bus';

const onClick = () => {
  console.log('调用第二个页面的方法');
  // 在这里执行您的业务逻辑
  bus.emit('callUpdateBankToUser');
}
</script>

在这个示例中,我们在 bus.ts 中创建了一个全局事件总线 bus。然后在第二个页面 user.vue 中,我们将 updateBankToUser 方法注册为名为 'callUpdateBankToUser' 的事件处理程序。在第一个页面 parent.vue 中的 onClick 方法中,我们使用 bus.emit 触发了 'callUpdateBankToUser' 事件,从而调用了第二个页面中的 updateBankToUser 方法。

通过这种方式,您可以在不直接引用第二个页面的情况下,实现跨组件调用其中的方法。

什么是全局事件总线bus.ts import mitt from 'mitt'; export const bus = mitt(); 两行的作用是啥

在这里,bus.ts 文件中的两行代码用来创建全局事件总线。全局事件总线是一个简单的事件管理器,它可以用于在应用程序中实现组件之间的通信,特别是当组件之间没有直接父子关系时。

具体来说,这两行代码的作用是:

  1. import mitt from 'mitt';: 这里我们导入了 mitt 库,它是一个简单、小巧的 JavaScript 事件库。mitt 库允许我们创建事件总线,以便在应用程序的任何地方都可以访问该事件总线。
  2. export const bus = mitt();: 在这里,我们创建了一个全局的事件总线 bus 并将其导出。通过 mitt() 调用,我们创建了一个新的事件总线实例,可以用来管理事件的注册、触发和取消。

总结起来,bus.ts 文件中的这两行代码的目的是创建一个全局的事件总线,以便在应用程序的任何地方都可以使用它进行组件之间的通信。通过这个全局事件总线,我们可以在不直接引用其他组件的情况下,实现组件之间的方法调用和数据传递。

]]>
<![CDATA[前端实现时间刷新]]> https://blog.iletter.top/index.php/archives/133.html 2023-05-24T23:48:00+08:00 2023-05-24T23:48:00+08:00 DelLevin https://blog.iletter.top 前端实现个时间刷新的案例:

标签

<div style="color:brown; font-size: 1.25rem /* 20/16 */;text-align: right;padding-right: 2.1875rem /* 35/16 */;;">
          {{ dataForm.currentTime }}</div>

script

const updateTime = () => {
  const currentDate = new Date();
  const year = String(currentDate.getFullYear());
  const month = String(currentDate.getMonth() + 1).padStart(2, '0');
  const date = String(currentDate.getDate()).padStart(2, '0');
  const hours = String(currentDate.getHours()).padStart(2, '0');
  const minutes = String(currentDate.getMinutes()).padStart(2, '0');
  const seconds = String(currentDate.getSeconds()).padStart(2, '0');
  dataForm.currentTime = `${year}年${month}月${date}日  ${hours}:${minutes}:${seconds}`;
};

这里主要调用的是 Date();的前端的方法。

然后我们设置定时器一秒刷新一次。

setInterval(() => {
  updateTime();
}, 1000);

这是根据人人开源项目做二次开发写的,其实二次开发真的有些难受。

如果要根据vue的方式来写就是这样的

<script>
export default {
  data() {
    return {
      currentTime: ''
    };
  },
  mounted() {
    // 更新时间
    this.updateTime();

    // 每秒钟更新一次时间
    setInterval(() => {
      this.updateTime();
    }, 1000);
  },
  methods: {
    updateTime() {
  const currentDate = new Date();
  const year = currentDate.getFullYear();
  const month = String(currentDate.getMonth() + 1).padStart(2, '0');
  const day = String(currentDate.getDate()).padStart(2, '0');
  const hours = String(currentDate.getHours()).padStart(2, '0');
  const minutes = String(currentDate.getMinutes()).padStart(2, '0');
  const seconds = String(currentDate.getSeconds()).padStart(2, '0');
  this.currentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    }
  }
};
</script>

mounted生命周期钩子中,我们通过调用updateTime方法来初始化时间,并使用setInterval函数每秒钟更新一次时间。

updateTime方法会获取当前时间并更新currentTime的值,然后将其显示在页面中。

getYear方法返回的是四位数的年份,getMonth方法返回的是从0开始的月份(需要加1),getDate方法返回的是月份中的日期。我们将它们转换为字符串并使用padStart方法进行补零操作,以保证两位数的格式。

最后,我们将年、月、日、时、分和秒拼接为一个完整的日期时间字符串,并将其赋值给currentTime,用于在页面中显示。

其中有个点就是在获取日期时,getDay()方法返回的是星期几(0-6),而不是日期。今天就犯糊涂了,用了getday()

另外推荐一个很好的拟态风格的css生成网站,简直不要太好用!!!!

https://neumorphism.io/#e0e0e0

]]>
<![CDATA[关于vue,ts,echart整合显示地图的实现方式]]> https://blog.iletter.top/index.php/archives/135.html 2023-05-24T23:48:00+08:00 2023-05-24T23:48:00+08:00 DelLevin https://blog.iletter.top 这两天需要做一个大屏的东西,看着网上那个地图的效果很酷很炫,所以想整一个这样的东西,话不多说,开始整。因为项目本体是人人开源项目,我是在这个基础上做的一个二次开发吧。后端代码就省略了。其实很简单。在此就不会做太多的赘述了。

后端

不过有一个点是可以关注的,就是当你的数据库过于庞大的话,我们可以建一个缓存。也就是一下的示例代码。

2023年5月25日:针对这个数据,因为map里面是放到缓存中了,一直没有释放,所以改用redis,又因为这个是二次开发的项目,所以人家对redis进行了一系列的改造,我在自己新建redis配置类的时候一直报错有冲突,所以在看了一下结构之后,对后端的代码进行了一系列的重构。

redis工具类

用于从 Redis 中获取缓存数据或者将数据存入 Redis


    public List<Object> getListFromRedis(String key) {
        Object cachedData = get(key);
        if (cachedData != null) {
            // 如果缓存中存在数据,则将其转换为 List 对象并返回
            return (List) cachedData;
        } else {
            // 如果缓存中不存在数据,则返回空列表
            return new ArrayList<>();
        }
    }
    public void setListToRedis(String key, List<Object> data, long expire) {
        set(key, data, expire);
    }

控制层

修改 getEnterpriseNums 方法,以先从 Redis 中获取数据,如果缓存中存在数据,则直接返回;如果缓存中不存在数据,则从数据库中获取数据,并将其存入 Redis 缓存中

    @Autowired
    private RedisUtils redisUtils;
    @GetMapping("/enterpriseNums")
    @ApiOperation("返回地图上的区域数据")
    public Result getEnterpriseNums(){
//        List<ShowEnterpriseDetailsNumsDTO> enterpriseDetailsList = enterpriseDetailsService.getEnterpriseNums();
//        return  new Result().ok(enterpriseDetailsList);
        String redisKey = "enterpriseNums"; // 定义 Redis 缓存的键名
        List enterpriseDetailsList = redisUtils.getListFromRedis(redisKey);
        if (enterpriseDetailsList.isEmpty()) {
            // 从数据库中获取数据
            enterpriseDetailsList = enterpriseDetailsService.getEnterpriseNums();
            // 将数据存入 Redis 缓存,设置过期时间为一小时
            redisUtils.setListToRedis(redisKey, enterpriseDetailsList, RedisUtils.HOUR_ONE_EXPIRE);
        }
        return new Result().ok(enterpriseDetailsList);
    }

首先从 showEnterpriseDetailsServicesCache 缓存中获取工厂信息列表数据。如果列表数据为空(即缓存中没有数据),则调用 enterpriseDetailsService.getEnterpriseNums() 方法获取工厂信息数据,并将其存入缓存中。在下次调用 getEnterpriseNums() 方法时,将直接从缓存中获取数据,避免了重复查询的开销。

通过以上优化,当第一次请求 getEnterpriseNums 接口时,数据将从数据库中获取,并存入 Redis 缓存中。之后的请求将直接从 Redis 缓存中获取数据,从而提高响应速度和性能。请确保 Redis 配置正确,并已经启动了 Redis 服务。

前端

echarts示例:https://echarts.apache.org/examples/zh/editor.html?c=map-HK

他给的示例我们可以分析出来,我觉得是通过ajax请求来把地图搞到,然后绑定map最后,把数据显示出来。$.get(ROOT_PATH + '/data/asset/geo/HK.json', function (geoJson)用于异步加载地图数据。这段代码的作用是通过 AJAX 请求获取名为 'HK.json' 的地图数据文件,然后在成功获取数据后,使用echarts.registerMap('HK', geoJson)将地图数据注册到 ECharts 中,并命名为 'HK'。这样,之后在配置地图的 series 中就可以通过指定 map: 'HK'` 来使用该地图数据。

再者我们需要提前的把地图数据导入到项目当中来。可以通过这个平台下载地图的数据

http://datav.aliyun.com/portal/school/atlas/area_selector#&lat=30.332329214580188&lng=106.72278672066881&zoom=3.5

然后的话导入方式,因为项目的原因我们需要这样做,

先是import

import jiNanMapJson from "src/views/enterprise/assets/json/济南市.json";

import * as echarts from "echarts";

再然后注册地图数据到 ECharts:

echarts.registerMap('jiNan', jiNanMapJson);

更新 mapOption 中的 series 配置,将 map: 'HK' 添加到相关的地图系列中,以便使用注册的地图数据:

const mapOption = ref({
  // ...其他配置项
  series: [
    {
      // ...其他系列配置
      map: 'jiNan', // 使用注册的地图数据
      // ...其他系列配置
    }
  ]
});

通过这种方式,我们可以注册到其中,完整代码如下:

<template>
  <div class="box">
    <el-form-item>
      <el-button type="warning" @click="importHandle()">{{ $t("excel.import") }}</el-button>
    </el-form-item>
    <div style="width: 100%;margin-left: 6em;margin-bottom: 2em;">
      <div class="topShow">
        <div style="color:brown; font-size: 1.875rem /* 30/16 */;text-align: center;padding-top: 1.4375rem /* 23/16 */;">
          信用金桥大数据屏幕展示平台</div>
        <div style="color:brown; font-size: 1.25rem /* 20/16 */;text-align: right;padding-right: 2.1875rem /* 35/16 */;;">
          {{ dataForm.currentTime }}</div>
      </div>
    </div>


    <div class="line_01">
      <figure class="figure_left">
        <div v-for="(item, index) in dataForm.enterpriseNums" :key="index" style="width: 23em;text-align: center; ">
          <div class="card">
            <span style="color: red;">{{ item.name }}区</span>
            在营企业总数 :
            <span style="color: sienna; font-size: 2em;">{{ item.value }} 家</span>
          </div>
          <br />
        </div>
      </figure>

      <figure class="figure_center">

      </figure>
      <figure class="figure_right">
        <v-chart :option="mapOption" :autoresize="true" />
      </figure>
    </div>
    <div class="line_02">
      <figure class="figure_left">
        <v-chart :option="pie" :autoresize="true" />
      </figure>
    </div>



    <import ref="importRef"></import>
  </div>
</template>

<script lang="ts" setup>
import { ref, provide, reactive } from "vue";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { BarChart, LineChart, PieChart, MapChart, RadarChart, ScatterChart, EffectScatterChart, LinesChart } from "echarts/charts";
import { GridComponent, PolarComponent, GeoComponent, TooltipComponent, LegendComponent, TitleComponent, VisualMapComponent, DatasetComponent, ToolboxComponent, DataZoomComponent } from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
import app from "@/constants/app";
import { getToken } from "@/utils/cache";
import Import from '@/views/enterprise/enterprise-library-import.vue';
import baseService from "@/service/baseService";
import jiNanMapJson from "src/views/enterprise/assets/json/济南市.json";
import zhangQiuMapJson from "src/views/enterprise/assets/json/章丘区.json";
import * as echarts from "echarts";

use([BarChart, LineChart, PieChart, MapChart, RadarChart, ScatterChart, EffectScatterChart, LinesChart, GridComponent, PolarComponent, GeoComponent, TooltipComponent, LegendComponent, TitleComponent, VisualMapComponent, DatasetComponent, CanvasRenderer, ToolboxComponent, DataZoomComponent]);

provide(THEME_KEY, "westeros");

const dataForm = reactive({
  currentTime: '',
  enterpriseNums: [],
});


const importRef = ref();
const importHandle = () => {
  importRef.value.init(`${app.api}/enterprise/details/import?token=${getToken()}`);
};

const list = ref([]);
const industryNames = ref([]);
const getIndustryInfo = async () => {
  let result = await (await baseService.get("/enterprise/details/industryInfo")).data;
  list.value = result;
  industryNames.value = result.map((item: any) => item.name);
};
getIndustryInfo();

const updateTime = () => {
  const currentDate = new Date();
  const year = String(currentDate.getFullYear());
  const month = String(currentDate.getMonth() + 1).padStart(2, '0');
  const date = String(currentDate.getDate()).padStart(2, '0');
  const hours = String(currentDate.getHours()).padStart(2, '0');
  const minutes = String(currentDate.getMinutes()).padStart(2, '0');
  const seconds = String(currentDate.getSeconds()).padStart(2, '0');
  dataForm.currentTime = `${year}年${month}月${date}日  ${hours}:${minutes}:${seconds}`;
};
setInterval(() => {
  updateTime();
}, 1000);

const mapMsg = ref([]);
const getEnterpriseNums = async () => {
  let result = await (await baseService.get("/enterprise/details/enterpriseNums")).data;
  dataForm.enterpriseNums = result;
  mapMsg.value = result;
  //console.log(mapMsg)
};
getEnterpriseNums();

echarts.registerMap('jiNan', jiNanMapJson);
const mapOption = ref({
  title: {
    text: '济南市各区在营企业统计',
    subtext: '数据来源XXXX',
  },
  tooltip: {
    trigger: 'item',
    formatter: '{b}<br/>{c} (家)'
  },
  toolbox: {
    show: false,
    orient: 'vertical',
    left: 'right',
    top: 'center',
    feature: {
      dataView: { readOnly: false },
      restore: {},
      saveAsImage: {}
    }
  },
  visualMap: {
    min: 800,
    max: 500000,
    text: ['High', 'Low'],
    realtime: false,
    calculable: true,
    inRange: {
      color: ['lightskyblue', 'yellow', 'orangered']
    }
  },
  series: [
    {
      name: '济南市各区注册企业数量',
      type: 'map',
      map: 'jiNan',
      label: {
        show: true
      },
      data: mapMsg,
    }
  ]
});


const pie = ref({
  title: {
    text: "行业占比",
    left: "center"
  },
  tooltip: {
    trigger: "item",
    formatter: "{a} <br/>{b} : {c} ({d}%)"
  },
  legend: {
    type: 'scroll',
    orient: "vertical",
    right: 10,
    top: 20,
    bottom: 20,
    left: "left",
    data: industryNames
  },
  series: [
    {
      name: "行业占比",
      type: "pie",
      label: {
        show: false
      },
      radius: "55%",
      center: ["50%", "60%"],
      data: list,
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: "rgba(0, 0, 0, 0.5)"
        }
      }
    }
  ]
});
</script>

<style lang="less" scoped>
.topShow {
  width: 90%;
  height: 5rem
    /* 80/16 */
  ;
  font-family: "得意黑";
  border-radius: 3.125rem
    /* 50/16 */
  ;
  border-radius: 3.4375rem
    /* 55/16 */
  ;
  border-radius: 2.5rem
    /* 40/16 */
  ;
  background: #f0f0ff;
  box-shadow: .4375rem
    /* 7/16 */
    .4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ccccd9,
    -.4375rem
    /* 7/16 */
    -.4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ffffff;
}

@font-face {
  font-family: '得意黑';
  src: url('./assets/font/SmileySans-Oblique-2.ttf');
  font-weight: normal;
  font-style: normal;
}

.card {
  border-radius: .5rem
    /* 8/16 */
  ;
  font-family: "得意黑";
  font-size: large;
  border-radius: 1.125rem
    /* 18/16 */
  ;
  background: #f0f0ff;
  box-shadow: inset .4375rem
    /* 7/16 */
    .4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ccccd9,
    inset -.4375rem
    /* 7/16 */
    -.4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ffffff;
}

.box {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;

  figure {
    display: inline-block;
    // position: relative;
    //margin: 2em auto;
    border: .0625rem
      /* 1/16 */
      solid rgba(0, 0, 0, 0.1);
    border-radius: .5rem
      /* 8/16 */
    ;
    box-shadow: 0 0 2.8125rem
      /* 45/16 */
      rgba(0, 0, 0, 0.2);
    padding: 1.875rem
      /* 30/16 */
    ;

    .echarts {
      width: 40vw;
      min-width: 25rem
        /* 400/16 */
      ;
      height: 18.75rem
        /* 300/16 */
      ;
    }
  }
}

.line_01 {
  width: 100%;
  height: auto;

  // 左对齐
  .figure_left {
    float: left;
    width: 27em;
    border-radius: 1.125rem
      /* 18/16 */
    ;
    background: #f0f0ff;
    box-shadow: .4375rem
      /* 7/16 */
      .4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ccccd9,
      -.4375rem
      /* 7/16 */
      -.4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ffffff;
  }

  //右对齐
  .figure_right {
    float: right;
    width: 50em;

    .echarts {
      width: auto;
      height: 43.75rem
        /* 700/16 */
      ;
    }

    border-radius: 1.125rem
    /* 18/16 */
    ;
    background: #f0f0ff;
    box-shadow: .4375rem
    /* 7/16 */
    .4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ccccd9,
    -.4375rem
    /* 7/16 */
    -.4375rem
    /* 7/16 */
    .875rem
    /* 14/16 */
    #ffffff;
  }

  .figure_center {
    //float: right;
    //position: absolute;
    width: 21em;
    border-radius: 1.125rem
      /* 18/16 */
    ;
    background: #f0f0ff;
    box-shadow: .4375rem
      /* 7/16 */
      .4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ccccd9,
      -.4375rem
      /* 7/16 */
      -.4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ffffff;
  }
}

.line_02 {
  width: 100%;
  height: auto;

  // 左对齐
  .figure_left {
    float: left;
    width: 50em;
    border-radius: 1.125rem
      /* 18/16 */
    ;
    background: #f0f0ff;
    box-shadow: .4375rem
      /* 7/16 */
      .4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ccccd9,
      -.4375rem
      /* 7/16 */
      -.4375rem
      /* 7/16 */
      .875rem
      /* 14/16 */
      #ffffff;
  }
}</style>

最终实现效果可参考echarts的案例。

]]>
<![CDATA[前端点击链接使用新建窗口打开外部链接]]> https://blog.iletter.top/index.php/archives/128.html 2023-05-12T23:48:00+08:00 2023-05-12T23:48:00+08:00 DelLevin https://blog.iletter.top 方法一:

我们可以使用a标签的属性来实现新建窗口跳转

<a :href="item.sciUrl" target="_blank">
</a>

其中target有以下几种属性

描述
_blank在新窗口中打开被链接文档。
_self默认。在相同的框架中打开被链接文档。
_parent在父框架集中打开被链接文档。
_top在整个窗口中打开被链接文档。
framename在指定的框架中打开被链接文档。

方法二:

我们可以使用js脚本来配合跳转

html部分

<span @click="See(url)">点击此处实现跳转</span>

js部分

See (e) {
        window.location.href = e
      }

其中url也就是你的传入值。

在这里面说明一下,你的链接在写的时候,必须带上http://或者https://如果不带的话,vue会自动识别为路由的

]]>