ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

开发案例-Vue Cli项目动态引入图片资源、Fastapi+PG库搭建web服务、自定义video元素控件

开发案例-Vue Cli项目动态引入图片资源、Fastapi+PG库搭建web服务、自定义video元素控件 Vue CliVue3项目使用v-bind动态控制img的src属性值踩坑记录之前自己写项目都是用ViteVue3突然接了个需要维护的项目使用Vue CliVue3构建的因为本人之前很少接触webpack流所以对其特性不是很了解导致踩了一些坑。踩坑记录-webpack项目img标签:src“urlPath“拿不到图片吐槽都2025年了怎么还有项目没用Vite呢简直比webpack流好用几十倍啊不论是启动还是热更新都遥遥领先。回到正题踩坑点使用img :srcimgs.img居然无法正常获取到图片。不使用v-bing绑定正常写入imgs.img的值时又可以正常显示图片。分析因为个人实在不想去了解过时的框架特性在C站上简单搜了下相关案例发现是脚手架的机制问题因为在Vite中就不会出现这种问题于是只能通过require动态导入资源。案例代码template!-- 一个3行2列的虚拟表格 --divclasstable_boxdivv-foritem in data:keyitem.keyclasscolimg:srcitem.imgaltclasscol_left_icondivclasscol_name{{ item.label }}/divdivclasscol_num{{ item.num item.unit }}/div/div/div/templatescriptsetuplangtsimport{ref}fromvueconstdataref([{img:require(../../assets/img/ck-icon1.png),key:key1,label:标签1,num:0,unit:个},{img:require(../../assets/img/ck-icon2.png),key:key2,label:标签2,num:0,unit:个},{img:require(../../assets/img/ck-icon3.png),key:key3,label:标签3,num:0,unit:条},// ... 共6条再次吐槽如果是Vite构建的项目此处无需使用require动态导入资源直接写相对路径即可。])/scriptstylelangscssscoped.table_box { height: 100%; width: 100%; display: flex; flex-wrap: wrap; //这个属性可以让弹性盒在适当的时候一般是宽度达到100%自动换行 justify-content: space-between; //结合上一条属性可以实现类似表格的效果 margin-top: 0.5vh; .col { height: 29%; width: 47%; margin: 0.4vh 0.5vh; display: flex; align-items: center; text-align: left; .col_left_icon { height: 60%; } .col_name { width: 38%; margin-left: 0.5vh; color: #fff; font-size: 0.7vw; white-space: nowrap; //强制不换行 } .col_num { width: 38%; margin-right: 0.5vh; color: aqua; font-size: 1vw; font-weight: 500; text-align: right; } } }使用FastApiPostgreSQL开发一个web服务端主要使用技术fastapi创建web服务器psycopg2连接数据库工具第三方库安装pip install fastapi搭建服务器pip install fastapi_cdn_host接口文档本地化部署pip install psycopg2连接pg数据库用pip install uvicorn启动服务器源码importosimporttimeimportjsonimportfastapi_cdn_host# 自动文档cdnimportconfigparser# 读取配置importpsycopg2# pg库工具fromdatetimeimportdatetime# 系统时间fromfastapiimportFastAPI,HTTPExceptionfrompydanticimportBaseModel,ConfigDictfromcontextlibimportasynccontextmanager# 创建一个读取配置文件的对象configconfigparser.ConfigParser()config.read(config.ini)# 获取配置信息db_hostconfig.get(pgdb,host)db_nameconfig.get(pgdb,database)db_userconfig.get(pgdb,user)db_passconfig.get(pgdb,password)db_portconfig.get(pgdb,port)# 创建连接对象connpsycopg2.connect(databasedb_name,userdb_user,passworddb_pass,hostdb_host,portdb_port)# print(Connected to PostgreSQL)curconn.cursor()# 获取游标# 使用asynccontextmanager装饰器来定义一个异步上下文管理器这将作为你的生命周期事件处理器asynccontextmanagerasyncdeflifespan(app:FastAPI):# 应用启动时执行的代码print(fFastAPI server started:{datetime.now()})yield# 应用关闭时执行的代码用于清理未关闭连接conn.close()print(fFastAPI server stopped:{datetime.now()})# 创建API接口appFastAPI(lifespanlifespan)# 引入本地cdn资源fastapi_cdn_host.patch_docs(app)# 参数类classQuery(BaseModel):# 借助 Pydantic 的BaseModel方法来使用标准的 Python 类型声明请求体sql:str# sql语句传参前端提供详细语句后端负责连接数据库执行dataSource:str# 数据源传参根据原始表的所属租户填写app.post(/pyapi/test/openQuery)deftest_open_query(query:Query):try:ifquery.dataSourcenotNone:print(query.dataSource,query.toJson)cur.execute(query.sql)return{status:200,msg:Operations Success!,data:cur.fetchall()}exceptExceptionase:raiseHTTPException(status_code500,detaile)classAnyParam(BaseModel):model_configConfigDict(extraallow)# 允许接收额外的字段app.post(/pyapi/test/pushPageVisits)deftest_push_page_visits(param:AnyParam):try:# 执行数据库插入数据操作cur.execute(INSERT INTO public.t_a_test_visits (username, employeeId, pageName, pageRoute, startTime, endTime, loginId) VALUES (%s, %s, %s, %s, %s, %s, %s),(param.username,param.employeeId,param.pageName,param.pageRoute,param.startTime,param.endTime,param.loginId))conn.commit()# 提交事务return{status:200,msg:Operations Success!}exceptExceptionase:conn.rollback()# 回滚事务raiseHTTPException(status_code500,detaile)app.post(/pyapi/test/getPageVisits)deftest_get_page_visits(param:AnyParam):# 执行数据库查询操作try:cur.execute(SELECT * FROM public.t_a_test_visits WHERE username coalesce(%s, username),(param.username,))return{status:200,msg:Operations Success!,data:cur.fetchall()}exceptExceptionase:raiseHTTPException(status_code500,detaile)# 启动服务器if__name____main__:os.system(chcp cls)importuvicorn uvicorn.run(main:app,host127.0.0.1,port8000,reloadTrue)# reload在开发时可设置为True在修改代码后会自动重启项目但打包时必须设置为False否则会导致应用被重复启动的问题原生video元素自定义控件背景项目需要对video元素做一些样式定义原生的很难修改于是借助video标签的事件进行自定义控件改造。本文内容基于网上大神分享的vue2方案改写为适配vue3的方案。原文链接掘金_原生video元素自定义播放控件controls开发环境Vue3 3.2Nodejs 18ElementPlus 2.3基础功能播放、暂停按钮当前播放时长/视频总时长进度条点击或拖动滑块可跳转进度并续播音量控制控件显隐源码templatedivclasscustomer_video_wrapmouseleavehideControlsmouseovershowControlsvideorefvideoPlayersrchttp://vjs.zencdn.net/v/oceans.mp4width100%:class[answerVideo, isMirror]preloadautotimeupdateupdateProgressBarloadedmetadatagetDurationendedonVideoEndedclicktogglePlay/video!-- 自定义视频控件 --transitionnameel-fade-in-lineardivclassvideo_controlv-showisShowingControlsstyletransition:opacity .5s;divclassvideo_control_topdivel-iconv-ifisPlayingclicktogglePlayVideoPause//el-iconel-iconv-elseclicktogglePlayVideoPlay//el-iconspanclassvideo_time{{ formatTime(currentTime) }}span/ {{ formatTime(duration) }}/span/span/divspanclassvideo_volume_wrapel-sliderclassvideo_volumev-modelvolumechangechangeVolume/el-slider音量/span/divel-sliderrefprogressSliderclassvideo_control_progressv-modelcurrentTime:show-tooltipfalse:maxdurationmousedownhandleDragStarttouchstarthandleDragStartchangechangeProgress/el-slider/div/transition/div/templatescriptsetupimport{VideoPause,VideoPlay}fromelement-plus/icons-vueimport{ref}fromvue// 自定义视频控件功能逻辑constisPlayingref(false)constcurrentTimeref(0)constdurationref(1)constvolumeref(30)constisShowingControlsref(true)constisDragingref(false)constvideoPlayerref(null)letvideo/** 视频播放/暂停 */consttogglePlay(){videovideoPlayer.valueif(video.paused){video.play()isPlaying.valuetrue}else{video.pause()isPlaying.valuefalse}}/** 视频播放结束 */constonVideoEnded(){isPlaying.valuefalse}/** 获取视频时长 */constgetDuration(){videovideoPlayer.valueif(video){duration.valueNumber.isFinite(video.duration)?video.duration:5}}/** 更新进度条 */constupdateProgressBar(){getDuration()if(isDraging.value){return}if(video){currentTime.valuevideo.currentTime}}/** 拖拽进度条结束时 */constchangeProgress(value){isDraging.valuefalsevideo.currentTimevalue video.play()isPlaying.valuetrue}/** 拖拽进度条开始与进行中时 */consthandleDragStart(){isDraging.valuetruevideo.pause()}/** 音量变化 */constchangeVolume(value){video.volumevalue/100}/** 格式化时间 */constformatTime(seconds){constformatval0${Math.floor(val)}.slice(-2)consthoursseconds/3600constminutes(seconds%3600)/60secondsseconds%60if(hours1){return[minutes,seconds].map(format).join(:)}return[hours,minutes,seconds].map(format).join(:)}/** 显示控制组件 */constshowControls(){isShowingControls.valuetrue}/** 隐藏控制组件 */consthideControls(){isShowingControls.valuefalse}/scriptstylescopedlangless.customer_video_wrap{position:relative;width:100%;}.answerVideo{z-index:1;}video.isMirror{transform:scale(-1,1);}// 自定义视频控件 .video_control{position:absolute;bottom:0;left:0;right:0;padding:8px 20px;background:linear-gradient(0deg,#000000 5%,#00000073,transparent);.video_control_top{display:flex;justify-content:space-between;align-items:center;color:#fff;i{font-size:20px;color:#fff;}.video_time{transform:translateY(-2px);display:inline-block;margin-left:8px;}.video_volume_wrap{display:flex;align-items:center;font-size:13px;}.video_volume{width:70px;margin-right:8px;}}:deep(.el-slider){.el-slider__runway{background-color:#cbcbcb;height:4px;margin:4px 0;}.el-slider__bar{height:4px;background-color:#fff;}.el-slider__button-wrapper{top:-16px;}.el-slider__button{width:10px;height:10px;border:none;}}}/style最终效果
返回列表