Tkinter+Flask+ThreeJS项目使用PyInstaller打包,做一个简简单单的表白程序!

        今天简单做一个使用Tkinter+Flask+ThreeJS制作的小小的表白程序,代码使用pyinstaller打包,项目虽简单但若掌握其技术要领,则可自己魔改,向心爱之人表白!

        一、开发环境

                1. python 3.8.9 64bit

                2. pycharm2021专业版

                3. tkinter 8.6

                4. flask 2.2.2

                5. threejs 0.157.0

                6. pyinstaller 6.3.0

        二、项目详情

        本示例项目运行的过程大概是这样的,首先会有一个打包好的exe文件,运行后则会出现一个问话框,之后则会自动打开一个网页,用于表白。

        首先是问话框的制作,这里使用python的第三方库tkinter制作。gui.py代码如下:

# -*- coding: UTF-8 -*-
# 请看下面代码
from tkinter import Tk, Button
from tkinter import messagebox
from sys import exit
from random import randint
from webbrowser import open as webopen
from icon import temp
from os import remove
from base64 import b64decode

root = Tk()  # 实例化
root.title("表白程序")  # 设置标题
root.geometry("300x200")  # 设置大小

fp = open('temp.ico', 'wb+')
fp.write(b64decode(temp))
fp.close()
root.iconbitmap('temp.ico')  # 设置图标
remove('temp.ico')


def dialog_one():
    result = messagebox.askyesno("对话框", "我喜欢你,你也喜欢我吗?")
    if result:
        webopen('127.0.0.1:8498')  # 打开网页
    else:
        messagebox.showwarning('警告', '好吧,溜了溜了')
        exit()


# x:0~250  y:0~170,制作一个按钮跳动的效果
def mouse_move(e):
    global count
    count -= 0.3
    if 40 >= count > 30:
        button.place(x=randint(0, 250), y=randint(0, 170))
    elif 30 >= count > 20:
        button.place(x=randint(0, 250), y=randint(0, 170))
    elif 20 >= count > 10:
        button.place(x=randint(0, 250), y=randint(0, 170))
    elif 10 >= count > 0:
        button.place(x=randint(0, 250), y=randint(0, 170))
    elif 0 >= count > -10:
        button.place(x=randint(0, 250), y=randint(0, 170))
        root.unbind('<Motion>')


count = 41
root.bind('<Motion>', mouse_move)  # 绑定鼠标事件
button = Button(root, text="点击我", fg='red', command=dialog_one)  # 实例化一个按钮
button.place(x=120, y=75)  # 放置按钮
root.mainloop()

            或许有的人会好奇16行到20行,只是设置一个图标为什么那么麻烦,不就是一句代码的事情吗?其实如果不这样处理的话,在打包后会出现找不到该图标的错误!目前我使用的解决办法如下:

        首先给大家看看我的项目文档结构:

        

        gui.py则是问话框文件,icon.ico是图标文件,icon.py用于存储icon.ico图标文件的二进制加密信息,而加密则是通过icoToBinary.py文件完成,icoToBinary.py文件内容为:

# -*- coding: UTF-8 -*-
# 请看下面代码
from base64 import b64encode

fp = open('icon.py', 'w+')
result = open('icon.ico', 'rb').read()
fp.write(f"temp = {b64encode(result)}")
fp.close()

        运行以上代码后,icon.py的内容为:

temp = b'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'

        请结合gui.py部分代码,也就能解释为什么16到20行代码这样写的原因了。

        前面说过,该项目还会有一个打开网页的效果,那么接下来就制作网页,后端由flask框架搭建。

        后端框架代码如下:

# -*- coding: UTF-8 -*-
# 请看下面代码
from flask import Flask, render_template
from flask_cors import CORS
import sys
from os import devnull

sys.stdout = open(devnull, "w")  # 不保留任何标准输出

app = Flask(__name__, template_folder='./')
CORS(app=app, supports_credentials='*')


@app.route('/')
def index():
    return render_template('index.html')


if __name__ == '__main__':
    app.run(port=6789, debug=True)

        若不加上第八行代码,打包运行会报错,加上就好。

        index.html文件内容为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body {
            background-color: pink;
            font-family: Arial, sans-serif;

        }

        h1 {
            text-align: center;
            font-size: 50px;
            color: white;
        }

        p {
            text-align: center;
            font-size: 30px;
            color: white;
        }

        button {
            display: block;
            margin: 0 auto;
            width: 200px;
            height: 50px;
            border: none;
            border-radius: 10px;
            background-color: white;
            font-size: 20px;
            color: pink;
        }

        button:hover {
            transform: scale(1.1);
        }

        button:active {
            transform: scale(0.9);
        }

        p {
            font-family: Arial, sans-serif;
            font-size: 30px;
            position: relative;
            left: -100px;
            animation: move 5s infinite alternate;
        }

        .data-text {
            z-index: 1;
        }


        @keyframes move {
            0% {
                color: red;
                transform: scale(1);
            }
            25% {
                color: orange;
                transform: scale(1.2);
            }
            50% {
                color: yellow;
                transform: scale(1.4);
            }
            75% {
                color: green;
                transform: scale(1.6);
            }
            100% {
                color: blue;
                transform: scale(1.8);
                left: 100%;
            }
        }
    </style>
    <script type="importmap">
        {
            "imports":{
                "three":"./static/three.module.js"
            }
        }

    </script>


</head>
<body>
<div id="app"></div>
<h1 class="data-text">我喜欢你!</h1>
<p class="data-text">你愿意做我的女朋友吗?</p>
<button class="data-text">答应我</button>
</body>
<script type="module">
    import * as THREE from 'three';
    import {OrbitControls} from './static/OrbitControls.js';

    let width = window.innerWidth
    let height = window.innerHeight
    const scene = new THREE.Scene()
    scene.background = new THREE.Color(0xFFC0CB)
    const camera = new THREE.PerspectiveCamera(60, width / height)
    camera.position.set(15, 15, 15)
    camera.lookAt(0, 0, 0)
    scene.add(camera)

    const material = new THREE.SpriteMaterial({
        map: new THREE.TextureLoader().load('./static/11.png')
    })

    const haoye = new THREE.Sprite(new THREE.SpriteMaterial({map: new THREE.TextureLoader().load('./static/haoye.png')}))
    haoye.position.set(0, -7, 0)
    haoye.scale.set(8, 8, 8)
    scene.add(haoye)
    const group = new THREE.Group();
    for (let i = 0; i < 200; i++) {

        const sprite = new THREE.Sprite(material);
        group.add(sprite);
        sprite.scale.set(1, 1, 1);

        const x = Math.random() * 25;
        const y = -Math.random() * 9;
        const z = Math.random() * -25;
        sprite.position.set(x, y, z)
    }
    for (let i = 0; i < 200; i++) {
        // 精灵模型共享材质
        const sprite = new THREE.Sprite(material);
        group.add(sprite);
        sprite.scale.set(1, 1, 1);
        // 设置精灵模型位置,在长方体空间上上随机分布
        const x = Math.random() * -25;
        const y = -Math.random() * 9;
        const z = Math.random() * 25;
        sprite.position.set(x, y, z)
    }
    for (let i = 0; i < 200; i++) {
        const sprite = new THREE.Sprite(material);
        group.add(sprite);
        sprite.scale.set(1, 1, 1);

        const x = Math.random() * -25;
        const y = -Math.random() * 9;
        const z = Math.random() * -25;
        sprite.position.set(x, y, z)
    }
    for (let i = 0; i < 200; i++) {
        // 精灵模型共享材质
        const sprite = new THREE.Sprite(material);
        group.add(sprite);
        sprite.scale.set(1, 1, 1);
        // 设置精灵模型位置,在长方体空间上上随机分布
        const x = Math.random() * 25;
        const y = -Math.random() * 9;
        const z = Math.random() * 25;
        sprite.position.set(x, y, z)
    }
    scene.add(group)

    const renderer = new THREE.WebGLRenderer()
    renderer.setSize(width, height)

    renderer.domElement.style.position = 'absolute'
    renderer.domElement.style.top = '0'
    renderer.domElement.style.left = '0'
    renderer.domElement.style.bottom = '0'
    renderer.domElement.style.right = '0'
    renderer.domElement.style.zIndex = '-1'

    const control = new OrbitControls(camera, renderer.domElement);
    (function animation() {
        group.children.forEach(sprite => {
            sprite.position.y += 0.1;
            if (sprite.position.y > 15) {
                sprite.position.y = -Math.random() * 20;
            }
        });
        renderer.render(scene, camera)

        requestAnimationFrame(animation)
    })();

    let button = document.querySelector('button')
    button.addEventListener('click', () => {
        document.querySelector('#app').appendChild(renderer.domElement)
    })

    window.onresize = () => {
        width = window.innerWidth
        height = window.innerHeight
        camera.aspect = width / height
        camera.updateProjectionMatrix()
        renderer.setSize(width, height)
    }

</script>
</html>

        此时,一切准备就绪!准备打包。此时的项目结构为:

        接下来准备打包,注意要cd到项目路径,使用如下命令分别对app.py和gui.py文件进行打包:

        1. pyinstaller -D -w -i icon.ico app.py

        2. pyinstaller -D -w -i icon.ico gui.py

        打包后会生成两个文件夹,进入dist文件夹,会看到有gui和app文件夹,里面就是打包好的可执行程序了,双击运行即可!运行步骤为:先运行app.exe,后运行gui.exe。也就是先打开后端,很好理解。当然运行app.exe时,进入“127.0.0.1:6789”网页时会找不到html文件,只需把index.html和对应的static静态资源复制到app文件夹下的“__internal”文件夹即可。

        

        再次进入网页就好了。请看效果:

        

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值