最近学习web.view。试了一下用web.view做程序界面,还是非常强大的,可以用很多前端的框架、插件等,灵活性很强,可以做出很炫的界面,这一点是winform界面编程很难比拟的。
下面代码是一个根据关键字查找文件名的小程序,不是很完善,功能也很简单,主要是想分享一下web.view做界面的过程,欢迎大家批评指正!
import win.ui; /*DSG{{*/ var winform = win.form(text="web.view制作界面: 查找文件名的小工具 (by:Mr_Mao)";right=1183;bottom=751) winform.add() /*}}*/ import fsys; import fsys.dlg.dir; import process; import fsys.info; import web.view; var wv = web.view(winform); wv.enableDefaultContextMenus(false) wv.external = { openDialog = function(){ var folder = fsys.dlg.dir(); return folder; }; isDir = function(str){ if(fsys.isDir(string.trim(str))) return true; }; openFile = function(path){ process.execute(path) }; deleteFile =function(path){ if(win.msgboxTest("你确定要删除这个文件吗?","请确认:",winform.hwnd)){ var bool = io.remove(path) //物理删除本地文件 if(bool) { sleep(300) wv.invoke("showDialog","提示:","文件已删除.") return true; } } } getIconBuffer = function(path){ var sfi = fsys.info.get(path, 0x100/*_SHGFI_ICON*/); var handle = sfi.hIcon; var buffer = gdip.bitmap(handle,1).saveToBuffer(".png") return buffer; }; searchFiles = function(folder,keyword,boolSubdir){ //清空listTable wv.doScript("document.getElementById('resultList').replaceChildren()") //枚举文件夹中的所有文件 thread.invoke( function(wv,folder,keyword,boolSubdir){ import fsys; import fsys.file; import inet.url; var i = 0; fsys.enum( folder, "*.*", function(dir,filename,fullpath,findData){ if(filename){ if(string.find(filename,"@"++ string.lower(keyword))){ i++; var file = fsys.file(fullpath); var size = file.size64().format(); //插入到x-data中的数组内 wv.invoke("add2items", i, filename, fullpath, size) //add2items(id,name,path,size) } } } ,boolSubdir/*是否包括子目录*/ ); wv.doScript("updateTooltips()") sleep(300) wv.invoke("showDialog","查找结果:","找到 "++i++" 个相符的文件.") },wv,folder,keyword,boolSubdir ) } ; } wv.html = /** <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bulma/1.0.3/css/bulma.css"> <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <script defer src="https://cdn.bootcdn.net/ajax/libs/alpinejs/3.14.9/cdn.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/colresizable/1.6.0/colResizable-1.6.min.js"></script> <style type="text/css"> .table { table-layout: fixed; width: 100%;} th:nth-child(1) { width: 62px;} th:nth-child(2) { width: 20%;} th:nth-child(3) { width: 120px;} th:nth-child(4) { width: auto;} th:nth-child(5) { width: 120px;} .truncate {/* 截断长文本并显示 "..." */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pagination-ellipsis { pointer-events: none; } </style> </head> <body> <div class="container" x-data="app" id="container"> <!-- form区域 --> <div class="section py-5"> <div class="columns is-vcentered"> <div class="column is-narrow"> <label class="label">选择文件夹:</label> </div> <div class="column"> <div class="field has-addons"> <div class="control has-icons-left is-expanded"> <input class="input" type="text" id="inputDir" x-model="dir" placeholder="c:\"> <span class="icon is-small is-left"> <i class="fa-solid fa-folder-open"></i> </span> </div> <div class="control"> <button class="button is-warning" @click="(async()=>{dir=await aardio.openDialog()})();">...</button> </div> </div> </div> </div> <div class="columns is-vcentered"> <div class="column is-narrow"> <label class="label">搜索关键字:</label> </div> <div class="column"> <div class="field"> <p class="control has-icons-left is-expanded"> <input class="input" type="text" id="inputKeyword" placeholder="输入要搜索的文件名,不支持模式匹配" x-model="word"> <span class="icon is-small is-left"> <i class="fa-solid fa-pen-to-square"></i> </span> </p> </div> </div> <div class="column is-narrow"> <div class="field is-horizontal"> <div class="field-body"> <div class="field is-grouped"> <div class="field is-narrow"> <div class="control"> <button class="button is-info px-5" @click.debounce="findStrInDir();"> <span class="icon is-small"> <i :class="isSearching?'fas fa-spinner fa-pulse':'fa-solid fa-magnifying-glass'"></i> </span> <span>查找文件名</span> </button> </div> </div> <div class="field is-narrow is-flex is-align-items-center"> <p class="control"> <label class="checkbox"><input type="checkbox" x-model="bool" class='mr-1'/>包含子文件夹</label> </p> </div> </div> </div> </div> </div> </div> </div> <!-- table区域 --> <div class="table-container mx-3"> <table class="table is-striped is-hoverable is-bordered" id="filestable"> <thead> <tr class="has-background-success"> <th class="has-text-centered is-size-7-touch" style="vertical-align: middle;">序号</th> <th class="has-text-centered is-size-7-touch" style="vertical-align: middle;">文件名</th> <th class="has-text-centered is-size-7-touch" style="vertical-align: middle;">文件大小</th> <th class="has-text-centered is-size-7-touch" style="vertical-align: middle;">文件路径</th> <th class="has-text-centered is-size-7-touch" style="vertical-align: middle;">操作</th> </tr> </thead> <tbody> <!-- aardio往这里写数据 --> <template x-for="item in pageItems"> <tr> <th class="has-text-centered" style="vertical-align: middle;" x-text="item.id"></th> <td class="truncate" style="vertical-align: middle;"> <figure class="image is-32x32 is-pulled-left"><img :src="showIcon(item.filepath)" /></figure> <span class="ml-2" x-text="item.filename"></span> </td> <td class="has-text-centered" style="vertical-align: middle;" x-text="item.filesize"></td> <td class="truncate" style="vertical-align: middle;" x-text="item.filepath"></td> <td style="vertical-align: middle;"> <div class="buttons are-small is-centered"> <button class="button is-info px-2 py-1" @click="openItem(item.filepath)">打开</button> <button class="button is-danger px-2 py-1" @click="(async()=>{if(await aardio.deleteFile(item.filepath)==true)deleteItem(totalItems, item.id); })()">删除</button> </div> </td> </tr> </template> </tbody> </table> </div> <!-- 分页器 --> <template x-if="totalPages>1"> <nav class="pagination is-centered" role="navigation" aria-label="pagination"> <ul class="pagination-list"> <li><!-- 上一页按钮 --> <a class="pagination-previous" :class="{ 'is-disabled': currentPage === 1 }" @click="changePage(currentPage - 1)"> 上页 </a> </li> <li> <a class="pagination-link" :class="{ 'is-current': 1 === currentPage }" @click="changePage(1)">1</a> </li> <li x-show="currentPage > visiblePages + 1"><!-- 前省略号 --> <span class="pagination-ellipsis">…</span> </li> <template x-for="page in visiblePageNumbers" :key="page"> <li x-show="page > 1 && page < totalPages"> <a class="pagination-link" :class="{ 'is-current': page === currentPage }" @click="changePage(page)" x-text="page"></a> </li> </template> <li x-show="currentPage < totalPages - visiblePages"><!-- 后省略号 --> <span class="pagination-ellipsis">…</span> </li> <li x-show="totalPages > 1"> <a class="pagination-link" :class="{ 'is-current': totalPages === currentPage }" @click="changePage(totalPages)" x-text="totalPages"></a> </li> <li><!-- 下一页按钮 --> <a class="pagination-next" :class="{ 'is-disabled': currentPage === totalPages }" @click="changePage(currentPage + 1)">下页</a> </li> </ul> </nav> </template> <!-- 模式对话框 --> <template x-teleport="body"> <div class="modal" :class="isModalOpen?'is-active':''"> <div class="modal-content" style="max-width:50%"> <article class="message is-info"> <div class="message-header"> <p x-text="title">title</p> <button class="delete" aria-label="delete" @click="isModalOpen=false;isSearching=false"></button> </div> <div class="message-body"> <div class="py-4" x-text="msg">contents</div> <div class="field is-grouped is-grouped-centered mt-5"> <button class="button is-primary mx-7" @click="isModalOpen=false;isSearching=false" @keyup.escape.window="$dispatch($el.click())">确 定</button> </div> </div> </article> </div> </div> </template> </div> <!-- div.container 结束 --> <script type="text/javascript"> //alpine初始化 document.addEventListener('alpine:init', function() { Alpine.data('app', () => ({ dir: '', word: '', bool: false, isSearching: false, isModalOpen: false, title: '', msg: '', totalItems:[], currentPage: 1, itemsPerPage: 10, visiblePages: 2, // 当前页左右各显示多少页码 get totalPages() { return Math.ceil(this.totalItems.length / this.itemsPerPage); }, get pageItems() { const start = (this.currentPage - 1) * this.itemsPerPage; const end = start + this.itemsPerPage; return this.totalItems.slice(start, end); }, changePage(page) { if (page >= 1 && page <= this.totalPages) { this.currentPage = page; } }, get visiblePageNumbers() { const start = Math.max(2, this.currentPage - this.visiblePages); const end = Math.min(this.totalPages - 1, this.currentPage + this.visiblePages); const pages = []; for (let i = start; i <= end; i++) { pages.push(i); } return pages; }, findStrInDir(){ this.isSearching=false; if(!this.dir){document.getElementById('inputDir').focus();return;}; if(!this.word){document.getElementById('inputKeyword').focus();return;}; (async()=>{ const isTrueDir = await aardio.isDir(this.dir); if(!isTrueDir){document.getElementById('inputDir').focus();return;}; this.isSearching=true; this.totalItems.length = 0; this.currentPage=1; aardio.searchFiles(this.dir,this.word,this.bool); })(); }, })) }); //显示一个提示框 function showDialog(t,m) { // 通过 DOM 元素获取 Alpine 组件实例 const component = document.querySelector('#container'); const alpineData = Alpine.$data(component); alpineData.title = t; alpineData.msg = m; alpineData.isModalOpen = true; } //添加符合条件的文件信息到数组中 function add2items(num,name,path,size) { const component = document.querySelector('#container'); const alpineData = Alpine.$data(component); alpineData.totalItems.push({id: num, filename: name, filesize: size, filepath: path}) } //打开本地文件 function openItem(path) { aardio.openFile(path) // } //删除本地文件 function deleteItem(arr, idNum) { arr.splice(arr.findIndex(el => el.id === idNum), 1) } //显示图标 async function showIcon(path) { var nativeByteArray = await aardio.getIconBuffer(path); const uint8Array = new Uint8Array(nativeByteArray); const blob = new Blob([uint8Array], { type: 'image/png' }); const imageUrl = URL.createObjectURL(blob); return imageUrl; } $(function() { //让表格中的列可调整 $("#filestable").colResizable(); }); //让td单元格鼠标悬停tip显示全名 const updateTooltips = () => { document.querySelectorAll('td').forEach(td => { const isOverflowing = td.scrollWidth > td.clientWidth; td.title = isOverflowing ? td.textContent : ''; }); }; window.addEventListener('resize', updateTooltips); </script> </body> </html> **/ wv.waitDoc() winform.show(); win.loopMessage();