最近学习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();


