【前端截屏上传存储于Caddy静态目录-签到系统的信息保存】

前端

按需引入

Vite官方脚本的步骤:

1. 首先环境初始
确保您已经安装了Node.js,pnpm。

在命令行中运行以下命令来全局安装Vite:
pnpm install -g create-vite
创建新的Vue项目:
create-vite my-vue-app --template vue
其中my-vue-app是您的项目名称。
进入项目文件夹:
cd my-vue-app
安装依赖:
pnpm install
启动开发服务器:
pnpm run dev
2. 使用Naive UI ,
是一个 Vue3 的组件库。全都可以 treeshaking。也就是按需导入.
参考https://www.naiveui.com/zh-CN/os-theme/docs/import-on-demand
我使用了这个配置,接下就可以用里面的库了. 过程中手工 pnpm install unplugin-auto-import/vite
unplugin-vue-components/vite’因为会有提示.

// vite.config.ts
import {
    
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
    
     NaiveUiResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
    
    
  plugins: [
    vue(),
    AutoImport({
    
    
      imports: [
        'vue',
        {
    
    
          'naive-ui': [
            'useDialog',
            'useMessage',
            'useNotification',
            'useLoadingBar'
          ]
        }
      ]
    }),
    Components({
    
    
      resolvers: [NaiveUiResolver()]
    })
  ]
})

显示功能组件

由于本次要显示打印表格https://www.naiveui.com/zh-CN/os-theme/components/data-table
把项目里的helloworld,全文替换成,页面里的示例.
由于太长不再提供,需要注意的是message这个组件.示例中用到,也会报错.但是改起了有点费劲. 因为message是和系统window绑定的,需要一个入口.
入口就是helloworld的上层,APP.vue. 在APP.vue中用 <n-message-provider> 包围<helloworld >, 才能使用提示窗口.我实验通过,但是弃用这个功能.
定义 vue的过程中, <scrite setup常出现在示例中,而我常消化不了这个语法,最后全换成了 没有它有时也会报错.
接下来的app.vue引入布局
<n-split size=“0.5”
是比例,不然可能会随页面内容变化,我需要中分所以是0.5,布局有各种,我只取这一种.这样能看到基本效果了.进入获取数据的下一步.

<n-gradient-text :size="26" type="error">
  {
   
   { title }}  
  </n-gradient-text><br>
 <h3 align="right">  会议:
    <font style="text-decoration: underline;margin-right:30px;">{
   
   { meeting }} </font>时间:
    <font style="text-decoration: underline;margin-right:30px;"> {
   
   {day}} </font>
   </h3>
 
<n-message-provider>
  <n-split
  direction="horizontal"
  style="height: 800px"
  max="300px"
  min="300px"
  size="0.5"
  default-size="300px"
>
<template #1>
  <HelloWorld ref="leftgd" :datas="lefts" />
  </template>
  <template #2>

    <HelloWorld :datas="rights"/>
  </template>
</n-split>
</n-message-provider>
   </div>
 </template>
 <style>
 h3 {
      
      
	margin-top: 0px;
	margin-bottom: 4px;
}
.n-gradient-text{
      
      
  letter-spacing: 15px;
}  
</style>

说下表格主体HelloWorld 这个表的margin ,也就是外框,和padding好像太大.所以用chrome的开发工具,拷贝css过来

<style lang="css">
 
table, th, td {
    
    
  border: 1px solid rgb(65, 44, 44);
  border-collapse: collapse; /* 移除单元格间的间隔 */
}
.n-data-table .n-data-table-td {
    
    
	padding: 4px;
  font-size : 16px;
  border: 1px solid rgb(65, 44, 44);
  border-collapse: collapse; /* 移除单元格间的间隔 */

	 }
   .n-data-table .n-data-table-th {
    
    
    padding: 8px;
    font-size : 20px;
    border: 1px solid rgb(65, 44, 44);
    border-collapse: collapse; /* 移除单元格间的间隔 */
    font-family: '黑体', 'Heiti SC', sans-serif; 
     }
</style>

变成紧凑有边框的样式.

服务器数据拉取

本来上一个模板所用的v-for 语法,可以支持,数据Arrary,的splice操作,进行切片,分成两半.
但是datatable的,数据源,定义必须不能这样用.
下面是方式,遍历均分成两个新建的ref([])

const fetchUsers = async () =>{
    
    
      try {
    
    
       const response = await fetch('./api/getcheckin');
     
       stas.value=await response.json();
        
         stas.value.map((v:RowData,k:number)=>{
    
    
      v["key"]=k
      v['note']=''
      v['names']= v['names'].join(',')
      return v
    })
    stas.value.forEach((v:RowData,k:number)=>{
    
    
       if (k<17){
    
    
                lefts.value.push(v)
       }
       else {
    
    

        rights.value.push(v)
       }
        })
    //   getCurrentInstance().$refs.leftgd.dataref.value=lefts.value
     //   console.log( stas.value);
      } catch (error ) {
    
    
        console.error('Fetch Error', error);
      }
    };

其中
v[“key”]=k
v[‘note’]=‘’
v[‘names’]= v[‘names’].join(‘,’)
key是必须的,按索引赋值, sta是原来就有的,names是格式需要变字符串,note是没用的备注.暂无信息.
这几个对应,HelloWorld,table的列定义

function createColumns( ): DataTableColumns<RowData> {
    
    
  return [
    {
    
    
      title: '站点',
      key: 'sta'
    },
    {
    
    
      title: '人员',
      key: 'names'
    },
    {
    
    
      title: '备注',
      key: 'note'
    },
  
     
  ]
}

调试通过以后这样
在这里插入图片描述

截屏包使用

在表头位置加入按钮保存,方法是产生主体的截屏,因为这些数据的redis只保存一天,会被覆盖,而且没有用数据库做后台,简化了配置.
注意最后一个 <div id=“maindiv”
这是用来获取内容的主体
<button @click=“takeScreenshot”>保存这是主要的功能

<template>
  <div style="width=140px;"><n-input autosize style="min-width: 20%" v-model:value="meeting"   type="text" placeholder="会议名称" />
  <button @click="takeScreenshot">保存</button>
   <a href="./checkin/"><button>浏览</button></a>
   <a href="javascript:window.history.back()"><button>返回</button></a>
  </div> 
  <div id="maindiv" 
   ref="screenshotElement" >

在scripte

import domtoimage from 'dom-to-image'

安装的时候注意使用 ts版本,因为默认安装了js的,build会报错.

pnpm i --save-dev @types/dom-to-image

这是报错指导的方法, 然后虽然baidu ai提示一段自用的编码来截屏,但是无法生效,这个库简洁明了,很是可爱.

主功函数,有两种保存方式,我注释了本地下载.link.a的那一整套.使用了上传post,API/putpng

//meeting后来加入的会议名,放在标题栏目下,可以任意定制,因为不需要服务器数据.只为模板图片保存.
 const meeting=ref("会议名称")
 const takeScreenshot = async () => {
    
    
      await nextTick(); // 确保DOM更新
      const node = document.getElementById('maindiv') // 通过id获取dom
      if (!node) return; //在ts下会提升node可能为空,所以是必须的.
      domtoimage
        .toPng(node)
        .then((dataUrl:string) => {
    
    
       //   const a = document.createElement('a') // 生成一个a元素
       //   const event = new MouseEvent('click') // 创建一个单击事件
         // a.download =getCurrentDate() // 设置图片名称没有设置则为默认
        //  a.href = "./checkin/" // 将生成的URL设置为a.href属性
     //  a.dispatchEvent(event) // 触发a的单击事件
       setTimeout( putpng ,10,(dataUrl))
             })
    
    };

上传截屏字节流需要注意的

上传数据函数putpng, 采用get参数应该是超长了,300K的base64,decode以后是200K多一点.四分一的扩展体积.参数里 key,dataurl是本次截取的数据,服务端要用.
这里本来没有跳转逻辑,window.location.href,放在这里很合适,毕竟不清楚,多久存储结束可以跳转.

const putpng = async (datastr:string) =>{
    
    
      try {
    
    
       const response = await fetch('./api/putpng', {
    
     method: 'POST', body: JSON.stringify({
    
     dataurl: datastr }), 
       headers: {
    
     'Content-Type': 'application/json' } });
     const r= await response.json()  
      if (r["OK"]==1){
    
    
        setTimeout( function (){
    
    window.location.href="./checkin/"+getCurrentDate()+'.png'} ,100)
       
      } 
      } catch (error ) {
    
    
        console.error('Fetch Error', error);
      }
    };

加批注保存浏览返回细节调整

就是上面提到 cost meeting=ref(“名称”),批注上会议名字, 然后用style美化一下边距.虽然依然丑.
用浏览,返回caddy管理的图片列表.要比nginx好太多.而且反代,配置依然简洁,后面会介绍
在这里插入图片描述

<template>
  <div style="width=140px;"><n-input autosize style="min-width: 20%" v-model:value="meeting"   type="text" placeholder="会议名称" />
  <button @click="takeScreenshot">保存</button>
   <a href="./checkin/"><button>浏览</button></a>
   <a href="javascript:window.history.back()"><button>返回</button></a>
  </div> 
  .....
  </tmplate>
  
   <style>
 h3 {
      
      
	margin-top: 0px;
	margin-bottom: 4px;
}
.n-gradient-text{
      
      
  letter-spacing: 15px;
}  
</style>

至此前端就完成了,主要提到了几个关键注意点,比如message, 按需引入,style样式更改,布局变化,datatable定义,后端将从flask入手.介绍算法的组成,和数据的安排.

后端

后端使用前大概已经做过了.vite.config.js

export default defineConfig({
    
    
  plugins: [vue()],
  server: {
    
    
    proxy: {
    
    
      '/api/': {
    
    
        target: 'http://127.0.0.1:8866/', // 目标服务器的域名
        changeOrigin: true, // 改变源到目标服务器
        // 其他可选配置...
      }
    }
  },
})

这样, server的api路径,会定义到dev环境5173的api

基础数据类型

  • Redis库.
    1,签到人员 key :check:{点位名}
    value,:集合 ,内容,人名字符串.
    2, 请假人员, key:heck:{点位名}:thin
    value, Hash, {“人名”:“原因”}
    此次只使用, Hash的keys.也就是人名列表,不使用所存内容.
  • 存储方式
    保存为文件,在flask可管理,caddy被指定哦file-server, 且broser的一个目录.
  • 编程语言python,其实可以是任何语言的.

签到API数据提取

@app.route("/api/getck")
def getck():
    #{ m:"a,n,c" ,sta:"Z"},
    listr=[]
    ck_sta=cl.keys("check:*")
    for i in ck_sta:
        checks=cl.smembers(i)
        staname=i.split(b':')[1]
        listr.append({
    
    'sta':staname.decode(),'names':[m.decode() for m in checks]})
    print(listr)
    return json.dumps(listr)

以上是只有做过签到的站点,
以目前的版式,必须均分,所以没有签到的也包含才合适,另外请假的信息也需要包含进来,更正主要部分为


 day=getday() # 2024-11-22
 #初始化时,根据模板,把每个站点,建立一个  日期/店名的key:list
 #然后key被存储在了 日期+keys里,这里是为了一个站点名称名称排序,
 #否则,keys是错乱的,所以在这里必须借鉴这一天的排序,站点名.由此提前check信息.
allstas=[i.decode() for i in cl.lrange(day+"keys",0,-1)]
 ck_sta=cl.keys("check:*")#包含两类数据 Set是签到  check:{sta}:thin,是Hash,
 stats=[i.split('/')[1]   for i in  allstats]  
   
for i in stats:
        if f"check:{
      
      i}".encode() in ck_sta:
            checks=cl.smembers(f"check:{
      
      i}")
            value=[m.decode() for m in checks]
             value.sort(key=lambda x:  stasdick[i] .index(x)) #需要根据名字模板排序,毕竟习惯老大,不能居于人下
             theone={
    
    'sta':i,'names':value} 
            if f"check:{
      
      i}:thin".encode() in ck_sta:
                thins= [i.decode() for i in   cl.hkeys(f"check:{
      
      i}:thin")]
                for N in thins:
                    if N in value:
                        value.remove(N)
                theone['names'].append('有事:('+'&'.join(thins)+")")
        else:
           theone={
    
    'sta':i,'names':[]}   #啥也没有
        listr.append(theone)

png图片的提取储存和展示

注意,获取纯base64的值,去除22位前的说明.这是用于直接的<img
下展示的东西.然后decode.转换成bytes.用bw模式打开文件,既可以保持.
或者的第二个接口, 格式png.直接发送给请求方,就是一个图片文件.

@app.route("/api/putpng",methods=['POST'])
def putpng():
   data = request.get_json()
   import base64
   dataraw=base64.b64decode(data['dataurl'][22:])
   with open('./checkin/'+datatime+'.png','bw')as fp:
       fp.write(dataraw)
   cl.set("imgtoday",dataraw)
   return json.dumps({
    
    "OK":1})
@app.route("/api/getpng")
def  getpng():
   data=cl.get('imgtoday')
   print(len(data))
   response = make_response(data)
   response.headers['Content-Type'] = 'image/png'
  # response.status=200
   return  response

关于caddy反代的配置文件

为了展示图片,原来打算用PanIndex,后来没有找到sfx的安装包.转入了caddy的怀抱,这是第三四次部署.也没太多难点了,开始还是不适用.主要在反代的路径配置上, 被反代的flask地址不能有路径, 必须到端口为止.这让后面的操作有点懵. 必须是转api的地址,但是转发后,默认,直接到根目录,要是也转发到api.需要使用rewrite指令.用法是试出来

:80 {
    
    
	# Set this path to your site's directory.
	root * /www/
   
    handle_path /api/* {
    
    
    rewrite * /api{
    
    path}
    reverse_proxy 1.1.1.25:7055 #此处为flask的服务和端口
    }
	# Enable the static file server.
	file_server {
    
    
         browse   #这样才能浏览目录
    }
	# Another common task is to set up a reverse proxy:
	# reverse_proxy localhost:8080
    # Or serve a PHP site through php-fpm:
	# php_fastcgi localhost:9000
}

我记得以前从版本80会自动跳转443,目前这个默认关闭这个功能,caddy外网会自动取得ssl正式.
效果
在这里插入图片描述
打开一个
在这里插入图片描述

至此本部分的功能结束,遗留的一大隐患是,必须人来操作保存,而不能自动化存储.因为存储前需要渲染页面,设定会议标题.这些可以用slimdafadsf,那个S开头的代理访问浏览器完成.我不想复杂化,
毕竟人工也是必不会少的部分.
由于checkin在二级目标, 反代以后, vite-vue项目 pnpm build 以后的dist目录可以直接甩到根目录下.省去了以前改来改去的烦恼.
因为nginx是借住的另外一个项目里的. 也是演示性质的.
caddy目前和flask分属不同容器,但公用一个目录.我不清楚为啥,现在啥权限也不用搞,就能读写. 也许是一开始就赋予高权限.
docker版本的ubuntu的安装vscode,1.93后,据说权限必须高级才能打开,为此.去docker commit了.然后,重新高权限启动一次4G以上的大约10G的大数据.
另外

新开的这个vue项目,在pnpm build的时候开启了严格检查, 每个类型空,无用的变量名,引用,都在报警.
记得刚开始接触vue的3年前, 差不多把我折腾疯了.
然而自从开始这个签到系统.还没遇到过这种检查.在这个功能实现的时候,又遇到一次,关闭的地方在项目根目录
package.json

  "scripts": {
    
    
    "dev": "vite",
    "build": "vite build",

build这样写
但后来

    "build": "vue-tsc -b && vite build",

在半夜还是改成了这样写.也许是vite帮了很大的忙. 快而准.这次不是太痛苦.
话说ts的基础为0的我,还是被 setup
export 折腾的要疯
别提那些react ,ascy, await , 这些奇怪的语法,
感觉在rust也都会遇到.

今天就这样.

猜你喜欢

转载自blog.csdn.net/wjcroom/article/details/143108162