vue3+element-plus+flask 简易【工作日志本】小软件(过程超详细)

news2024/11/14 20:52:29

终于有时间继续学习技术了!开发了一个简易的用于记录日常工作内容的小软件,权当学习和练手。功能如下:用户登录、日志内容的查、增、删、改以及导出。
开发环境:
windows 10,mysql 8,Hbuilder X(最近vs code总是崩溃)
生产环境:
windows,mysql, Nginx,Waitress

1 数据库设计

数据库名:mywork
users表结构:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `un` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pwd` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `truename` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

works表结构:

DROP TABLE IF EXISTS `works`;
CREATE TABLE `works`  (
  `workid` int(11) NOT NULL AUTO_INCREMENT,
  `date` date NULL DEFAULT NULL,
  `weekday` varchar(9) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `worktype` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `supervisetype` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  PRIMARY KEY (`workid`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

2 前端实现

21.1 创建vue3 项目

npm init vue@3

cd frontend[这是我的前端项目名称]
npm install

npm install axios --save
npm install element-plus --save
npm install xlsx --save

2.2 修改 index.html文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>工作日志本</title>
    <style>
      #app{
          background-image: url('/src/assets/meetingroom.jpg');
          background-size: cover;
          background-repeat: no-repeat;
          height: 98vh;
          width: 99vw;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

2.3 修改main.js文件

import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn' //设置前端时区

axios.defaults.withCredentials = true  //允许跨域
axios.defaults.baseURL = 'http://127.0.0.1:8080/'  //设置后端api前缀

const app = createApp(App)
app.use(ElementPlus, {
    locale: zhCn,
  })
app.use(router)
app.mount('#app')

2.4 修改App.vue文件

<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

<style scoped>
</style>

2.5 修改router/index.js路由配置文件

import { createRouter, createWebHistory } from 'vue-router'
import login from '../components/login.vue'
import index from '../components/index.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/login',
      name: 'login',
      component: login
    },
    {
      path: '/',
      name: 'index',
      component: index
    },{
      path: '/index',
      name: 'index',
      component: index
    }
  ]
})
export default router

2.6 login.vue文件(登录组件)

<template>
    <div>
        <div style="padding-top: 20rem;"></div>
        <div class="loginBox" @keypress.enter="submithandle">
            <div style="text-align: center;">
                <h1>工作日志本</h1>
            </div>
            <div>
                <el-form-item label="用户名">
                    <el-input v-model="un" />
                </el-form-item>
            </div>
            <div>
                <el-form-item label="密&emsp;码">
                    <el-input v-model="pwd" type="password" autocomplete="off" />
                </el-form-item>
            </div>
            <div  style="text-align: center;">
                <el-button type="primary" round @click="submithandle">提交</el-button>
                <el-button type="info" round>重置</el-button>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import axios from "axios";
import { ElMessage } from 'element-plus'

const un = ref('')
const pwd = ref('')
const curRouter = useRouter()

function submithandle() {
    if (un.value == "" || pwd.value == "") {
        ElMessage({
            message: '请填完整再提交。',
            type: 'error',
        })
    }
    else {
        axios.post("/login", { "un": un.value, "pwd": pwd.value }).then(rs => {
            if (rs.data.code == 200) {
                ElMessage({
                    message: '登录成功!',
                    type: 'success',
                })
                curRouter.push('/')
            }
            else {
                ElMessage({
                    message: '用户名或密码不正确,请重试',
                    type: 'error',
                })
            }
        })
    }

}
</script>
<style>
.loginBox{
    width: 30rem;
    margin: 0 auto;
    border: 1px solid gray;
    border-radius: 5px;
    padding: 1rem;
    background-color: rgba(255, 255, 255, 0.8);
}
</style>

2.7 index.vue文件(核心功能组件)

<template>
    <div>
        <el-container>
            <!-- 菜单栏 -->
            <el-header
                style="background: linear-gradient(90deg, #409eff 50%, #a12cc1 100%);border-bottom: 2px solid white;">
                <el-row>
                    <el-col :span="8"></el-col>
                    <el-col :span="8" style="text-align: center;color: white;">
                        <h1>工作日志本</h1>
                    </el-col>
                    <el-col :span="8" style="text-align: right;">
                        <el-dropdown style="line-height: 70px;">
                            <span class="el-dropdown-link" style="color: white;">
                                {{ truename }}
                                <el-icon class="el-icon--right">
                                    <arrow-down />
                                </el-icon>
                            </span>
                            <template #dropdown>
                                <el-dropdown-menu>
                                    <el-dropdown-item>修改密码</el-dropdown-item>
                                    <el-dropdown-item @click="logout">退出登录</el-dropdown-item>
                                </el-dropdown-menu>
                            </template>
                        </el-dropdown>
                    </el-col>
                </el-row>
            </el-header>

            <!-- 工作区 -->
            <el-main>
                <div>
                    <el-button type="success" @click="showDraw('添加')">添加</el-button> &emsp;
                    <el-date-picker v-model="queryformdata.querydate" type="daterange" range-separator="至" start-placeholder="开始日期"
                        end-placeholder="结束日期" size="default"  value-format="YYYY-MM-DD" format="YYYY-MM-DD" clearable />&emsp;
                    <el-select v-model="queryformdata.worktype" placeholder="--工作类型--" style="width: 150px" clearable><el-option
                            v-for="item in worktypelist" :key="item.value" :label="item.label" :value="item.value" />
                    </el-select>&emsp;
                    <el-select v-model="queryformdata.supervisetype" placeholder="--监督方式--" style="width: 150px" clearable><el-option
                            v-for="item in supervisetypelist" :key="item.value" :label="item.label"
                            :value="item.value" />
                    </el-select>&emsp;
                    <el-input v-model="queryformdata.content" style="width: 240px" placeholder="关键词"
                        :prefix-icon="Search" clearable/>&emsp;
                    <el-button type="success" @click="handleQuery">查询</el-button>
                    <el-button type="success" @click="handleExport">导出</el-button>
                    <el-button type="success" @click="getTableData">全部</el-button>

                </div>
                <el-divider />
                <div>
                    <el-table
                        :data="tableData.slice((statePager.currentPage - 1) * pagesize, statePager.currentPage * pagesize)"
                        border style="width: 100%">
                        <el-table-column prop="workid" label="编号" width="80" />
                        <el-table-column prop="date" label="日期" width="150" />
                        <el-table-column prop="weekday" label="星期" width="100" />
                        <el-table-column prop="content" label="工作内容" width="800" />
                        <el-table-column prop="worktype" label="工作类型" width="150" />
                        <el-table-column prop="supervisetype" label="监督方式" width="150" />
                        <el-table-column prop="remark" label="备注" />
                        <el-table-column label="操作" width="200">
                            <template #default="scope">
                                <el-button @click="handleEdit(scope.$index, scope.row, '修改')" type="primary">
                                    修改
                                </el-button>
                                <el-button type="danger" @click="handleDelete(scope.$index, scope.row)">
                                    删除
                                </el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </div>
                <div style="background-color: white;padding-top: 1rem;">
                    <el-pagination @current-change="handleCurrentChange" layout="total, prev, pager, next"
                        :page-size="pagesize" background :total="tableData.length"></el-pagination>
                </div>
            </el-main>
        </el-container>
    </div>

    <!-- 编辑数据抽屉 -->
    <el-drawer v-model="isshowdrawer" direction="rtl" size="500px">
        <template #header>
            <h4>{{ acttype }}</h4>
        </template>
        <template #default>
            <div>
                <el-form :model="formData" label-width="auto" style="max-width: 600px">
                    <el-form-item label="&emsp;&emsp;日期">
                        <el-col>
                            <el-date-picker v-model="formData.date" type="date" placeholder="选择日期" style="width: 100%"
                                clearable="" value-format="YYYY-MM-DD" format="YYYY-MM-DD" />
                        </el-col>
                    </el-form-item>

                    <el-form-item label="工作内容">
                        <el-input v-model="formData.content" type="textarea" :rows="8" />
                    </el-form-item>

                    <el-form-item label="工作类型">
                        <el-select v-model="formData.worktype" placeholder="--请选择--" clearable><el-option
                                v-for="item in worktypelist" :key="item.value" :label="item.label"
                                :value="item.value" />
                        </el-select>
                    </el-form-item>

                    <el-form-item label="监督方式">
                        <el-select v-model="formData.supervisetype" placeholder="--请选择--" clearable><el-option
                                v-for="item in supervisetypelist" :key="item.value" :label="item.label"
                                :value="item.value" />
                        </el-select>
                    </el-form-item>

                    <el-form-item label="&emsp;&emsp;备注">
                        <el-input v-model="formData.remark" type="text" />
                    </el-form-item>

                </el-form>

            </div>
        </template>
        <template #footer>
            <div style="flex: auto">
                <el-button @click="cancelClick">取消</el-button>
                <el-button type="primary" @click="confirmClick">确定</el-button>
            </div>
        </template>
    </el-drawer>

</template>

<script setup>
    // 引入功能
    import {
        ref,
        onMounted,
        reactive
    } from "vue";
    import {
        useRouter
    } from "vue-router";
    import axios from "axios";
    import {
        ElMessage,
        ElMessageBox
    } from 'element-plus'
    import {
        Search
    } from '@element-plus/icons-vue'
    import * as XLSX from "xlsx";

    // 以下为定义组件所需变量
    const curRouter = useRouter() //用于路由跳转的变量

    //接收后端传来的登录用户信息
    const uid = ref('')
    const un = ref('')
    const truename = ref('')

    //查询功能相关变量
    const queryformdata=reactive({
        querydate:'',
        querydatefrom:'',
        querydateto:'',
        worktype:'',
        supervisetype:'',
        content:''
    })
    
    // 工作类型选项
    const worktypelist = [{
        value: '111',
        label: '111',
    }, {
        value: '222',
        label: '222',
    }]
    
    // 工作方式选项
    const supervisetypelist = [{
            value: '111',
            label: '111',
        }, {
            value: '222',
            label: '222',
        }
    ]
    const querykeywords = ref('')

    //数据表呈现
    const tableData = ref([]) //表格数据
    const statePager = reactive({
        currentPage: 1
    }); //用于保存当前表格页的变量
    const pagesize = ref(10) //表格中每页数据条数

    // 表格翻页按钮
    const handleCurrentChange = (e) => {
        statePager.currentPage = e;
    };

    //抽屉相关
    const isshowdrawer = ref(false) //控制抽屉是否展现,默认不展现
    const acttype = ref('') //记录用户打开抽屉的行为类型,拟为“添加”和“修改”
    const formData = reactive({
        workid: '',
        date: '',
        content: '',
        worktype: '',
        supervisetype: '',
        remark: ''
    }) //保存抽屉中表单数据的变量

    //组件加载时的动作,作用:一是检测是否登录,二是获取用户信息并拉取表格数据
    onMounted(() => {
        // 检测登录状态
        axios.post("login/islog").then((rs) => {
            // console.log(rs.data.code)
            if (rs.data.code != 200) {
                ElMessage({
                    message: '您尚未登录,即将跳转至登录页面。',
                    type: 'error',
                })
                curRouter.push('/login');
            } else {
                uid.value = rs.data.uid;
                un.value = rs.data.un;
                truename.value = rs.data.truename;
                getTableData()
            }
        })
    })

    // 退出登录
    function logout() {
        ElMessageBox.confirm(
                '确定要退出登录吗?',
                'Warning', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning',
                }
            )
            .then(() => {
                axios.post("login/logout").then((rs) => {
                    if (rs.data.code == 200) {
                        curRouter.push('/login');
                    }
                })
            })
            .catch(() => {

            })
    }

    // 拉取表格数据
    function getTableData() {
        axios.get("works").then((rs) => {
            tableData.value = rs.data.data;
        })
    }
    
    //查询函数
    function handleQuery(){
        if(queryformdata.querydate=='' && queryformdata.worktype=='' && queryformdata.supervisetype=='')
        {
            getTableData()
        }
        else
        {
            axios.post("works/filter",{data: queryformdata}).then((rs)=>{
                tableData.value = rs.data.data;
            })
        }
    }
    
    function handleExport(){
        const titleArr = ['编号','日期','星期','工作内容','工作类型','工作方式','备注','操作']//表头中文名
        exportExcel(tableData.value, '我的工作日志', titleArr, 'sheetName');
    }
    
    /*
        把表格数据导出到excel中的函数
        * @description:
        * @param {Object} json 服务端发过来的数据
        * @param {String} name 导出Excel文件名字
        * @param {String} titleArr 导出Excel表头
        * @param {String} sheetName 导出sheetName名字
        * @return:
        */
    function exportExcel(json, name, titleArr, sheetName) {
          /* convert state to workbook */
          var data = new Array();
          var keyArray = new Array();
          const getLength = function (obj) {
            var count = 0;
            for (var i in obj) {
              if (obj.hasOwnProperty(i)) {
              count++;
              }
            }
          return count;
          };
          for (const key1 in json) {
          if (json.hasOwnProperty(key1)) {
            const element = json[key1];
            var rowDataArray = new Array();
            for (const key2 in element) {
            if (element.hasOwnProperty(key2)) {
              const element2 = element[key2];
              rowDataArray.push(element2);
              if (keyArray.length < getLength(element)) {
              keyArray.push(key2);
              }
            }
            }
            data.push(rowDataArray);
          }
          }
          // keyArray为英文字段表头
          data.splice(0, 0, keyArray, titleArr);
          console.log('data', data);
          const ws = XLSX.utils.aoa_to_sheet(data);
          const wb = XLSX.utils.book_new();
          // 此处隐藏英文字段表头
          var wsrows = [{ hidden: true }];
          ws['!rows'] = wsrows; // ws - worksheet
          XLSX.utils.book_append_sheet(wb, ws, sheetName);
          /* generate file and send to client */
          XLSX.writeFile(wb, name + '.xlsx');
        }

    // 用户点击数据表中编辑按钮的事件函数,意图是把表格中的某行数据读取到表单中
    function handleEdit(index, row, tempacttype) {
        isshowdrawer.value = true
        acttype.value = tempacttype
        formData.workid = row.workid
        formData.date = row.date
        formData.content = row.content
        formData.worktype = row.worktype
        formData.supervisetype = row.supervisetype
        formData.remark = row.remark
    }

    // 用户点击数据表中删除按钮的事件函数,
    function handleDelete(index, row) {
        ElMessageBox.confirm('确定要删除编号为【' + row.workid + '】的内容吗?', 'Warning', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            })
            .then(() => {
                axios.delete("works/" + row.workid).then((rs) => {
                    if (rs.data.code == 200) {
                        getTableData()
                    }
                })
            })
            .catch(() => {})
    }

    //控制抽屉展现的函数
    function showDraw(tempacttype) {
        isshowdrawer.value = true
        acttype.value = tempacttype
        formData.workid = ''
    }

    //控制抽屉关闭的函数
    function cancelClick() {
        isshowdrawer.value = false
    }

    // 用户点击抽屉中“确定”按钮事件的函数,
    function confirmClick() {
        //如果意图是新增
        if (acttype.value == '添加') {
            axios.post('works', {
                data: formData
            }).then((rs) => {
                if (rs.data.code == 200) {
                    getTableData()
                    ElMessage({
                        message: '添加成功,您可以继续添加。',
                        type: 'success',
                    })
                } else {
                    ElMessage({
                        message: '添加失败,请重试或联系开发人员。',
                        type: 'error',
                    })
                }
            })
        }
        // 如果意图是编辑
        else if (acttype.value == '修改') {
            axios.put('works/' + formData.workid, {
                data: formData
            }).then((rs) => {
                console.log(formData)
                if (rs.data.code == 200) {
                    getTableData()
                    isshowdrawer.value = false
                    ElMessage({
                        message: '修改成功!',
                        type: 'success',
                    })
                } else {
                    ElMessage({
                        message: '修改失败,请重试或联系开发人员。',
                        type: 'error',
                    })
                }
            })
        }
    }
</script>

<style scoped>
    .el-header {
        --el-header-height: 70px;
    }

    .el-divider--horizontal {
        margin: 0.5rem 0;
    }

    .el-table {
        --el-table-text-color: #000000;
        font-size: 16px;
    }
</style>

3 后端实现

后端使用Python Flask框架实现,使用Blueprint功能,开发了RESTfull风格的相关接口。

3.1 后端主入口文件app.py

from flask import Flask 
from flask import request
import pymysql
import json
from flask_cors import CORS
from flask import session
import time
import os
from flask import Blueprint, jsonify

#导入两个Blueprint模块
from api.login import login
from api.works import works

#设置时区
os.environ['TZ'] = 'Asia/Shanghai' 

app = Flask(__name__)

#json数据不根据key名进行排序
app.config['JSON_SORT_KEYS'] = False 

# 以下代码设置程序代码更新后自动重启web服务,仅用于开发过程中。
app.debug = True
app.config.update(DEBUG=True)

CORS(app, supports_credentials=True) #允许跨域访问

#以下代码解决跨域时设置session值无效的问题
app.config['SESSION_COOKIE_SAMESITE'] = "None"  # 设置samesite 为None
app.config['SESSION_COOKIE_SECURE'] = True  # SECURE 为 true 
app.secret_key="guoxiyue"

#注册两个Blueprint实例
app.register_blueprint(login,url_prefix='/')
app.register_blueprint(works,url_prefix='/')

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

3.2 api/login.py文件

from flask import Blueprint, jsonify, request,session
import pymysql

db = pymysql.connect(host="localhost", port=3306, user='root', password='123456', charset='utf8', database='mywork', cursorclass=pymysql.cursors.DictCursor) #连接数据库
mycursor = db.cursor() #创建游标对象

login=Blueprint('login',__name__)

# 用户登录
@login.route(rule="/login",methods=['post'])
def dologin():
    data=request.json
    un=data['un']
    pwd=data['pwd']
    #查询数据库
    sql = "select * from users where un='"+un+"' and pwd=md5('"+pwd+"')" 
    mycursor.execute(sql)  
    rs = mycursor.fetchone() 

	#处理查询结果
    if rs is not None:  #如果查询结果不为空,则创建session
        session['uid']=rs['uid']
        session['un']=rs['un']
        session['truename']=rs['truename']
        return jsonify({'code':200,'uid':rs['uid'],'un':rs['un'],'truename':rs['truename']})
    else:
        return jsonify({'code':400})


# 查询登录状态
@login.route(rule="/login/islog",methods=['post'])
def islog():
    un = session.get('un')
    if un is None:
        return jsonify({"code":404,"msg":"not logged"})
    else:
        return jsonify({'code':200,'uid':session.get('uid'),'un':session.get('un'),'truename':session.get('truename')})


# 退出登录
@login.route(rule="/login/logout",methods=['post'])
def logout():
    session.clear()
    return jsonify({"code":200,"msg":"logout"})

3.3 api/works.py文件

from flask import Blueprint, jsonify,request
import pymysql

db = pymysql.connect(host="localhost", port=3306, user='root', password='123456', charset='utf8', database='mywork', cursorclass=pymysql.cursors.DictCursor) #连接数据库
mycursor = db.cursor() #创建游标对象
mycursor.execute("SET @@lc_time_names = 'zh_CN';")

works=Blueprint('works',__name__)

# 查询所有数据(即无条件查询)
@works.route(rule="/works",methods=['GET'])
def getworksList():
    sql = "select workid,date_format(`date`,'%Y-%m-%d') as `date`,DAYNAME(`date`) as `weekday`,content,worktype,supervisetype,remark from works order by `date` desc,workid desc" 
    mycursor.execute(sql)  
    rs=mycursor.fetchall()
    return jsonify({"code":200,"data":rs})

# 查询部分数据(即有条件查询)
@works.route(rule="/works/filter",methods=['post'])
def getFilteWorksList():
    filterfields=""
    data=request.json
    # print(data)
    if data['data']['querydate'] is not None and data['data']['querydate']!="":
        querydatefrom=data['data']['querydate'][0]
        querydateto=data['data']['querydate'][1]
        filterfields+=" `date` between '"+querydatefrom+"' and '"+querydateto+"' "
    if 'worktype' in data['data'] and data['data']['worktype'] is not None and data['data']['worktype']!="":
        filterfields+=" and `worktype` = '"+data['data']['worktype']+"' "
    if 'supervisetype' in data['data'] and data['data']['supervisetype'] is not None and data['data']['supervisetype']!="":
        filterfields+="and `supervisetype` = '"+data['data']['supervisetype']+"' "
    if data['data']['content'] is not None and data['data']['content']!="":
        filterfields+="and `content` like '%"+data['data']['content']+"%' "
    filterfields=filterfields.strip(" ") #去掉首尾空格
    if filterfields[0:3]=="and": 
        filterfields=filterfields[3:] #去掉打头的and
    sql = "select workid,date_format(`date`,'%Y-%m-%d') as `date`,DAYNAME(`date`) as `weekday`,content,worktype,supervisetype,remark from works where "+filterfields+" order by `date` desc,workid desc" 
    mycursor.execute(sql)  
    rs=mycursor.fetchall()
    # print(sql)
    return jsonify({"code":200,'data':rs})
    
# 查询单条记录
@works.route(rule="/works/<workid>",methods=['GET'])
def getworksById(workid):
    # tempid = request.args.get('worksid')
    return jsonify({"code":200,"msg":"get workid: "+str(workid)+", OK!"})

# 新增记录
@works.route(rule="/works/",methods=['POST'])
def addUser():
    data=request.json
    date=data['data']['date'][0:10]
    content=data['data']['content']
    worktype=data['data']['worktype']
    if "supervisetype" in data['data']:
        tempsql=data['data']['supervisetype']
    else:
        tempsql=""

    # supervisetype=data['data']['supervisetype']
    remark=data['data']['remark']
    sql="insert into works values(null,'"+date+"',null,'"+content+"','"+worktype+"','"+tempsql+"','"+remark+"')"
    # print(sql)
    mycursor.execute(sql)
    db.commit()
    return jsonify({"code":200,"msg":"insert works, OK!"})

# 修改记录
@works.route(rule="/works/<int:workid>",methods=['PUT'])
def updateUser(workid):
    data=request.json
    date=data['data']['date'][0:10]
    content=data['data']['content']
    worktype=data['data']['worktype']
    supervisetype=data['data']['supervisetype']
    remark=data['data']['remark']
    sql="update works set `date`='"+date+"',content='"+content+"',worktype='"+worktype+"',supervisetype='"+supervisetype+"',remark='"+remark+"' where workid="+str(workid)
    mycursor.execute(sql)
    db.commit()
    return jsonify({"code":200,"msg":"update worksid: "+str(workid)+", OK!"})

# 删除记录
@works.route(rule="/works/<int:workid>",methods=['DELETE'])
def deleteUser(workid):
    sql="delete from works where workid="+str(workid)
    mycursor.execute(sql)
    db.commit()
    return jsonify({"code":200,"msg":"delete worksid: "+str(workid)+", OK!"})

4 生产环境部署

这里采用Nginx+Waitress来实现。Nginx和Waitress的安装过程略。

4.1 编译前端项目

在前端项目根目录运行以下命令,得到编译后的项目文件,在dist目录中

npm run build

把dist目录内容复制到Nginx的www目录下即可完成前端程序的部署。

4.2 配置后端项目

在后端项目的根目录下,创建run.bat文件,内容如下:

start waitress-serve --listen=127.0.0.1:5000 app:app

双击run.bat即可运行Waitress服务。

打开nginx的配置文件,在http模块中添加以下内容:

	server{
		listen 8080;
		location / {
			proxy_pass http://127.0.0.1:5000;
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		}
	}

以上代码的意思是,通过nginx服务来反向代理Waitress运行的Flask程序。即:把http://127.0.0.1:5000 访问转为http://127.0.0.1:8080,这样就实现了由Nginx来接管Flask程序的运行,但是运行Flask的Waitressp进程窗口不能关闭。重启Nginx后生效。

4.3 生产环境所需服务

一是MySQL服务,二是Nginx服务,三是Waitress进程,以上三者全部运行以后,打开浏览器,访问 http://127.0.0.1 即可运行项目。

5 运行界面截图

登录界面
数据展示界面
数据编辑界面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990230.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux -- 进度条小程序

目录 一、缓冲区 二、回车与换行 三、进度条 1、版本一 2、版本二 在写小程序之前先来了解两个知识点 一、缓冲区 缓冲区(buffer)&#xff0c;它是内存空间的一部分。也就是说在内存空间中预留了一定的存储空间&#xff0c;这些存储空间用来缓冲输入或者输出的数据&#…

【eNSP模拟实验】交换机调整stp根端口和配置边缘端口

拓扑 关闭提示和设备重命名 将S1~S4交换机都做如下相关对应的配置 <Huawei>sys [Huawei]un in en [Huawei]sys S1 调整根端口 当前S3交换机生成树简要信息&#xff0c;其中e0/0/1是阻塞端口&#xff0c;e0/0/5是根端口。如何让e0/0/5变成阻塞端口&#xff0c;让e0/0/1…

【面试题】IDEA实现Debug远程调试Linux中的系统

有朋友面试时被问到&#xff0c;怎么远程调试部署在Linux中的系统&#xff1f;听到这个问题&#xff0c;那位朋友直接懵了&#xff0c;第一反应是震惊&#xff0c;已经部署在Linux中的系统还能调试&#xff1f; 沉默了几秒&#xff0c;只好说没有远程调试过Linux中的系统&#…

Debian 12 Linux系统安装Mongodb服务器步骤

在本地或云中运行的 Debian12 或 11 Linux 发行版上设置 MongoDB 数据库服务器并不是一件困难的事情&#xff0c;但是&#xff0c;必须知道如何使用终端和 Linux 命令行。虽然 MongoDB 除了社区版之外还提供了企业版&#xff0c;但在这里我们将使用这个免费的开源 NoSQL 文档数…

Spring基础知识学习总结(四)

&#xff08;5&#xff09;Spring新注解 使用上面的注解还不能全部替代xml配置文件&#xff0c;还需要使用注解替代的配置如下&#xff1a; 非自定义的Bean的配置&#xff1a;<bean>加载properties文件的配置&#xff1a;<context:property-placeholder>组件扫描…

Linux 文件、重定向、缓冲区

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a; Linux 目录 一、文件 1、文件的理解&#xff08;浅层&#xff09; 1.文件是什么&#xff1f; 2.文件操作的前提 3.文件的存储 4.一个进程可以打开多个文件吗&#xff1f;如果可以怎么管理的&#xf…

【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式

文章目录 webView使用步骤示例 HttpURLConnection使用步骤示例GET请求POST请求 okHttp使用步骤1. 添加依赖2. 创建OkHttpClient实例3. 创建Request对象构建请求4. 发送请求5. 获取响应 Pull解析方式1. 准备XML数据2. 创建数据类3. 使用Pull解析器解析XML webView WebView 是 An…

三大浏览器Google Chrome、Edge、Firefox内存占用对比

问题 Chrome、Edg、Firefox三家究竟谁的占用少 结论 打开一个页面内存占用 Firefox>Edge>Chrome 打开打量页面内存占用 Firefox>Chrome>Edge 从监视器可以看到Edge增加一个页面增加一个页面不到100M而其它浏览器需要150M左右;Firefox浏览器主线程内存占用800M比…

Java之TCP网络编程

TCP网络编程 1 概述 在TCP通信协议下&#xff0c;计算机网络中不同设备上的应用程序之间可以通信&#xff0c;通信时需严格区分客户端&#xff08;Client&#xff09;与服务器端&#xff08;Server&#xff09;。 在Java中&#xff0c;对于这样基于TCP协议下连接通信的客户端…

数据结构-常见排序的七大排序

1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录…

Arkose 验证码:网络抓取工具的最佳实践

网络爬虫已经成为企业和开发人员最常用的工具&#xff0c;用于有效地从网络中收集数据。当然&#xff0c;大家都会遇到的最常见挑战是大量的 CAPTCHA 出现&#xff0c;这会使工作流程陷入停滞。其中&#xff0c;Arkose Labs Captcha&#xff08;称为 Funcaptcha&#xff09;以其…

探索N卡录制:游戏录屏工具会是网页录屏的最佳伴侣吗?

在这个数字时代&#xff0c;无论是游戏玩家、教育工作者还是内容创作者&#xff0c;高质量的录屏工具都是必不可少的。NVIDIA显卡&#xff08;简称N卡&#xff09;以其卓越的图形处理能力而闻名&#xff0c;而N卡录制功能则进一步扩展了其应用范围&#xff0c;特别是在游戏录屏…

【SpringBoot 属性加载机制】

SpringBoot 属性加载 一个 SpringBoot 应用的配置属性可以有多种不同的来源, 比如可以来自操作系统的环境变量, 比如可以来自 application.yaml 文件; 每一种不同的属性来源, 都会被 SpringBoot 封装成一个PropertySource对象, 保存在 Environment 对象的 PropertySources 类型…

proteus仿真c51单片机(三)多定时任务的编程——时分秒发生器

实验步骤 1、打开PROTEUS软件选取元件&#xff0c;连线绘制电路图。 2、打开KEIL软件编制程序进行调试、编译。 3、将编译的HEX代码装入PROTEUS软件绘制的电路图的单片机中。 4、仿真运行观察实验现象&#xff0c;若与要求的实验现象不同&#xff0c;检查程序和电路图找…

Linux目录结构常用命令和文件管理解析

一.Linux目录结构 bin:binary二进制&#xff0c;普通用户执行命令都在这里 boot:和计算机启动的相关文件都放这 dev:device设备、驱动、硬件 etc:配置文件也叫控制台文件 home:来装用户自己的文件的&#xff0c;普通用户的家目录主目录&#xff0c;每个普通用户进来都有自…

PythonStudio 控件使用常用方式(十二)TMaskEdit

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;它使用的是Delphi的控件&#xff0c;常用的内容是与Delphi一致的。但是相关文档并一定完整。现在我试试能否逐步把它的控件常用用法写一点点&#xff0c;也作为PythonStudio的参考。 从1.2.1版开始&#xff0c;Python…

Android compose OutlinedTextField 输入框设置固定高度后,内容挤压显示不全

原因&#xff1a; decorationBox里边contentPadding() 默认为16.dp internal val TextFieldPadding 16.dp 修改方法1&#xff1a; copy OutlinedTextField.kt 源码&#xff0c;decorationBox的contentPadding参数开放出来 /*** author 创建人&#xff1a;蒙石瑞* date …

【大模型从入门到精通11】openAI API 提示链的力量2

这里写目录标题 提示链的力量&#xff1a;阅读和处理JSON字符串基于产品信息生成用户响应综合性的客户服务互动结论理论问题 提示链的力量&#xff1a;阅读和处理JSON字符串 当处理复杂的工作流程时&#xff0c;通常会以JSON格式传递数据。以下示例演示了如何将JSON字符串转换…

【Linux QT】添加Json-C库

前言 在Linux应用开发中&#xff0c;Linux设备和服务器通信时&#xff0c;两者之间数据的传输通常采用JSON数据格式来作为载体&#xff0c;便于两者之间的数据交互。当设备端接收到服务端下发的JSON数据&#xff0c;设备端需要对JSON格式的数据进行解析&#xff1b;当设备端需要…

Java设计模式(命令模式)

定义 将一个请求封装为一个对象&#xff0c;从而让你可以用不同的请求对客户进行参数化&#xff0c;对请求排队或者记录请求日志&#xff0c;以及支持可撤销的操作。 角色 抽象命令类&#xff08;Command&#xff09;&#xff1a;声明用于执行请求的execute方法&#xff0c;通…