内部资源站

内部资源站

概述

一个基于 Python Flask 开发的轻量级内部资源导航站,引用 Element UI 样式,方便管理和快速查找公司内部各种工具和平台链接。

核心功能

  • 分类展示:按研发工具、运维工具、云平台等分组展示资源
  • 卡片式布局:每个资源以卡片形式展示图标和名称
  • 多环境切换:支持同一资源多个环境(内网/生产/测试)的弹窗选择
  • 数据驱动:通过 JSON 配置文件动态渲染页面,增删改无需修改代码

适用场景

  • 公司内部导航页
  • 团队工具集汇总
  • 运维/开发资源入口

部署

目录结构

./
│   data-v2.json    # 配置文件
│   main.py         # Python 主程序
│   README.md
│
├───static
│   │   favicon.ico
│   │   index.css   # Element UI 样式
│   │   index.js    # Element UI 脚本
│   │   vue.js      # Vue.js 框架
│   │
│   └───img
│           1panel.png
│           aliyun.png
│           archery.png
│           arweb.png
│           baota.png
│           cdn.png
│           jenkins.png
│           zabbix.png
│
└───templates
        index.html   # 页面模板

Python 主程序(main.py)

from flask import Flask, render_template, jsonify
import json

app = Flask(__name__)

# 修改 Jinja2 默认语法,避免与 Vue.js 的 {{ }} 冲突
app.jinja_env.variable_start_string = '[['
app.jinja_env.variable_end_string = ']]'

# 读取并返回 data-v2.json 文件内容
@app.route('/data')
def get_data():
    try:
        with open('data-v2.json', 'r', encoding='utf-8') as file:
            data = json.load(file)
        return jsonify(data)
    except FileNotFoundError:
        return jsonify({"error": "File not found"}), 404
    except json.JSONDecodeError:
        return jsonify({"error": "Error decoding JSON"}), 500

# 渲染 index.html 页面
@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080)

HTML 页面模板(templates/index.html)

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内部资源站</title>
    <link rel="icon" href="static/favicon.ico">
    <!-- 引入 Element UI 样式 -->
    <link rel="stylesheet" href="static/index.css">
    <script src="static/vue.js"></script>
    <script src="static/index.js"></script>
</head>

<body>

    <div id="app">
        <div class="list" v-for="(item, itemindex) in database" :key="itemindex">
            <h3 style="padding-left: 10px;">{{ item.indexname }}</h3>
            <div class="table">
                <el-card class="card" shadow="hover" v-for="app in item.appgroup" :key="app.urlname">
                    <template v-if="app.lables">
                        <img :src="app.img" class="image">
                        <el-popover placement="right" trigger="click">
                            <el-table :data="app.env" max-height="450">
                                <el-table-column prop="urlname" label="环境">
                                    <template slot-scope="scope">
                                        <el-link :href="scope.row && scope.row.url" target="_blank">
                                            {{ scope.row.urlname }}
                                        </el-link>
                                    </template>
                                </el-table-column>
                            </el-table>
                            <el-button round slot="reference" size="mini">{{ app.urlname }}</el-button>
                        </el-popover>
                    </template>
                    <template v-else>
                        <img :src="app.img" class="image" @click="urljump(app.url)">
                        <div align="center"><span>{{ app.urlname }}</span></div>
                    </template>
                </el-card>
            </div>
        </div>
    </div>

    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    database: []
                };
            },
            created() {
                fetch('/data')
                    .then(response => response.json())
                    .then(data => {
                        this.database = data;
                    })
                    .catch(error => console.error('Error fetching data:', error));
            },
            methods: {
                urljump(url) {
                    window.open(url);
                }
            }
        });
    </script>

    <style>
        .table {
            display: flex;
            flex-wrap: wrap;
        }

        .card {
            margin: 10px;
            box-sizing: border-box;
            width: 120px;
        }

        .el-card__body {
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            padding: 10px !important;
        }

        .image {
            width: 70px;
            height: 70px;
            display: inline-block;
            border-radius: 10px;
            margin-bottom: 10px;
            cursor: pointer;
        }
    </style>
</body>

</html>

配置文件(data-v2.json)

根据配置文件数据格式展示内容,添加配置项即为添加展示的资源卡片:

[
  {
    "indexname": "研发工具",
    "appgroup": [
      {
        "urlname": "Jenkins",
        "img": "static/img/jenkins.png",
        "lables": true,
        "env": [
          {
            "urlname": "内网",
            "url": "http://内网地址"
          },
          {
            "urlname": "生产",
            "url": "http://生产地址"
          },
          {
            "urlname": "测试",
            "url": "http://测试地址"
          }
        ]
      },
      {
        "urlname": "IT Tools",
        "img": "static/img/it-tools.png",
        "url": "static/it-tools/"
      }
    ]
  },
  {
    "indexname": "运维工具",
    "appgroup": [
      {
        "urlname": "Zabbix",
        "img": "static/img/zabbix.png",
        "url": "http://zabbix地址"
      }
    ]
  },
  {
    "indexname": "云平台",
    "appgroup": [
      {
        "urlname": "腾讯云",
        "img": "static/img/tx.png",
        "url": "https://console.cloud.tencent.com"
      },
      {
        "urlname": "阿里云",
        "img": "static/img/aliyun.png",
        "url": "https://console.aliyun.com"
      }
    ]
  }
]

启动服务

安装依赖

pip install flask

运行服务

python main.py

访问地址

http://<服务器IP>:8080

后台运行(推荐)

# 使用 nohup
nohup python main.py > /dev/null 2>&1 &

# 或使用 screen / tmux 保持会话

配置文件字段说明

字段 类型 必填 说明
indexname string 分组名称,如"研发工具"
appgroup array 该分组下的应用列表
appgroup.urlname string 应用显示名称
appgroup.img string 应用图标路径(相对路径)
appgroup.lables boolean 是否启用多环境弹窗(true/false)
appgroup.env array 多环境列表(lables 为 true 时生效)
appgroup.env.urlname string 环境名称,如"内网"、“生产”
appgroup.env.url string 环境对应的访问地址
appgroup.url string 单链接地址(lables 为 false 或不设置时使用)

常见问题与注意事项

Vue.js 与 Jinja2 语法冲突

Flask 的 Jinja2 模板引擎默认使用 {{ }},与 Vue.js 的插值语法冲突。代码中已修改 Jinja2 分隔符:

app.jinja_env.variable_start_string = '[['
app.jinja_env.variable_end_string = ']]'

图标不显示

  1. 确认图片文件存在于 static/img/ 目录下
  2. 检查配置文件中的 img 路径是否正确(如 static/img/xxx.png
  3. 确认文件名大小写与实际文件一致

新增资源不显示

  1. 确认 data-v2.json 文件格式正确(使用 JSON 校验工具检查)
  2. 检查是否有多余的逗号或缺少引号
  3. 重启服务使配置生效

端口被占用

# 查看端口占用
netstat -tlnp | grep 8080

# 修改 main.py 中的端口
app.run(host="0.0.0.0", port=8090)

访问页面空白

  1. 确认 templates/index.html 存在
  2. 检查静态资源(static/ 目录)是否完整
  3. 打开浏览器开发者工具,查看 Console 是否有报错

多环境弹窗不显示

确认配置中 lables 字段为 true,且 env 数组不为空:

{
  "urlname": "Jenkins",
  "img": "static/img/jenkins.png",
  "lables": true,
  "env": [
    { "urlname": "内网", "url": "http://xxx" },
    { "urlname": "生产", "url": "http://xxx" }
  ]
}

参考链接