VUE截屏上传存储于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><h3align="right"> 会议:
<fontstyle="text-decoration: underline;margin-right:30px;">{{ meeting }} </font>时间:
<fontstyle="text-decoration: underline;margin-right:30px;"> {{day}} </font></h3><n-message-provider><n-splitdirection="horizontal"style="height: 800px"max="300px"min="300px"size="0.5"default-size="300px"><template#1><HelloWorldref="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([])
constfetchUsers=async()=>{try{const response =awaitfetch('./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><divstyle="width=140px;"><n-inputautosizestyle="min-width: 20%"v-model:value="meeting"type="text"placeholder="会议名称"/><button@click="takeScreenshot">保存</button><ahref="./checkin/"><button>浏览</button></a><ahref="javascript:window.history.back()"><button>返回</button></a></div><divid="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("会议名称")consttakeScreenshot=async()=>{awaitnextTick();// 确保DOM更新const node = document.getElementById('maindiv')// 通过id获取domif(!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><divstyle="width=140px;"><n-inputautosizestyle="min-width: 20%"v-model:value="meeting"type="text"placeholder="会议名称"/><button@click="takeScreenshot">保存</button><ahref="./checkin/"><button>浏览</button></a><ahref="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
exportdefaultdefineConfig({
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")defgetck():#{ 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:iff"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}iff"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'])defputpng():
data = request.get_json()import base64
dataraw=base64.b64decode(data['dataurl'][22:])withopen('./checkin/'+datatime+'.png','bw')as fp:
fp.write(dataraw)
cl.set("imgtoday",dataraw)return json.dumps({"OK":1})@app.route("/api/getpng")defgetpng():
data=cl.get('imgtoday')print(len(data))
response = make_response(data)
response.headers['Content-Type']='image/png'# response.status=200return 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也都会遇到.
今天就这样.
版权归原作者 wjcroom 所有, 如有侵权,请联系我们删除。