webix.protoUI({
name: "xFileManager",
$init(config) {
this.$view.config = config;
this.$ready.push(this._require_map);
},
async _require_map() {
let that = this;
let plugins_url = this.$view.config.plugins_url;
webix.require([
plugins_url + "/vue/vue.min.js", // vue
plugins_url + "/filemanager/filemanager.js", // filemanager 脚本
plugins_url + "/filemanager/filemanager.css", // filemanager 样式
plugins_url + "/jquery/jquery.js", // jquery
], () => {
that.vueScript(that);
});
},
FilemanagerHTML(config) {
// 头部dom
let header = `
<div id='vue_${config.dom_id}' class='vue_app' style='height:100%;width:100%;position:relative'>
<div id="mountNode" style='height:100%;width:100%;position:relative'></div>
`
// 主体dom
let container = "文件管理"
// 底部dom
let bottom = `
</div>
</div>
`
return header + container + bottom
},
/**
* @desc webix-protoUI 结合 vue2的具体函数
* @param {*} webixSuper 这是webix的Super对象,指向了webix的this
*/
vueScript(webixSuper) {
webixSuper.$view.innerHTML = webixSuper.FilemanagerHTML(this.$view.config);
let xFilemanager = new Vue({
el: `#vue_${this.config.dom_id}`,
data: {
message: 'xFileManager',
config: this.$view.config,
currentPath: "",
vues: [],
},
mounted() {
this.WebixFileManager();
window.addEventListener('resize', this.calculateZoom);
},
destroyed() {
window.removeEventListener('resize', this.calculateZoom);
},
watch: {
},
methods: {
WebixFileManager() {
let that = this;
let container = document.getElementById('mountNode');
const FOLDER = 0x01; // folders (any folders in tree and right side)
const TEXT = 0x02; // code type files
const VIEW = 0x04; // files that can be opened in a browser (some audio files are downloadable)
const COMPACT = 0x08; // compact mode
const SEARCH = 0x10; // menu opened in search (search results)
const FILE = 0x20; // files
const SINGLE = 0x40; // menu is opened for 1 item (file or folder)
const RIGHT = 0x80; // menu is opened on the right side (item or empty space)
const ITEMS = 0x100; // files and folders in both parts
webix.ready(function () {
if (!webix.env.touch && webix.env.scrollSize) {
webix.CustomScroll.init();
}
function reloadDirs() {
const data = $$("xfm").getService("local");
data.folders(true);
}
function reloadFiles() {
const data = $$("xfm").getService("local");
const path = $$("xfm").getState().path;
data.refresh(path);
}
class MyBackend extends fileManager.services.Backend {
folders() {
const self = this; // 用于确保我们能够在回调中访问 this
// 使用 jQuery 发送 POST 请求
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("folders.php"), // 目标 URL(你可以替换成实际的 API 地址)
type: 'POST', // 请求方式
contentType: 'application/json', // 请求体类型
data: JSON.stringify({
root: that.config.root,
}), // 传递的数据
success: function (response) {
// 如果请求成功,解析返回的 JSON 数据
resolve(JSON.parse(response)); // 返回数据
},
error: function (xhr, status, error) {
// 如果请求失败,处理错误
reject(error); // 返回错误
}
});
});
}
files(id) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("files.php"),
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
root: that.config.root,
id: id,
}),
success: function (response) {
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
_files(url, params) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("files.php"),
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
root: that.config.root,
id: params.id,
search: params.search, //查询条件
filter: params.filter,
}),
success: function (response) {
console.log(response);
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
getInfo(force) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("info.php"),
type: 'POST',
contentType: 'application/json',
success: function (response) {
resolve(JSON.parse(response));
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
icon(obj, size) {
const self = this;
return this.url(
`icons.php/${webix.skin.$name}/${size || 'small'}/${obj.type}/${obj.$ext}/svg`
);
}
rename(id, name) {
const self = this;
$.ajax({
url: self.url("rename.php"), // 目标 URL(你可以替换成实际的 API 地址)
type: 'POST', // 使用 POST 请求
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件
name: name,
}), // 传递的数据
contentType: 'application/json', // 请求体类型
}).then((response) => {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadFiles();
});
}
copy(id, to) {
const self = this; // 用于确保我们能够在回调中访问 this
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("copy.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id,//可以是文件夹可以是文件
to: to, //必须是文件夹路径
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
directLink(id, download) {
const self = this;
return new Promise((resolve, reject) => {
// 构造请求 URL
const url = `${self.url("direct.php")}?root=${encodeURIComponent(that.config.root)}&fid=${encodeURIComponent(id)}&download=${download}`;
// 检查是否是图片文件
const fileExtension = id.split('.').pop().toLowerCase();
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(fileExtension); // 支持的图片格式
// 如果是图片,添加水印
if (isImage) {
const img = new Image();
img.src = url;
img.crossOrigin = 'anonymous';
img.onload = function () {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const text = homeMenuX.userName + homeMenuX.userAccount;
const font = '30px Arial';
const fillStyle = 'rgba(255, 255, 255, 0.5)';
const angle = Math.PI / 4;
const horizontalSpacing = 150;
const verticalSpacing = 150;
ctx.font = font;
ctx.fillStyle = fillStyle;
const textWidth = ctx.measureText(text).width;
const textHeight = parseInt(font, 10);
let xPos = canvas.width - textWidth;
let yPos = 0;
while (yPos < canvas.height) {
while (xPos > 0) {
ctx.save();
ctx.translate(xPos, yPos);
ctx.rotate(-angle);
ctx.fillText(text, 0, 0);
ctx.restore();
xPos -= (textWidth + horizontalSpacing);
}
yPos += (textHeight + verticalSpacing);
xPos = canvas.width - textWidth;
}
canvas.toBlob(function (blob) {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = id.split('/').pop();
link.click();
resolve('下载开始');
}, 'image/jpeg');
};
img.onerror = function (error) {
reject('图片加载失败:' + error);
};
} else {
// 如果不是图片,直接下载文件
const link = document.createElement('a');
link.href = url;
link.download = id.split('/').pop();
link.click();
resolve('下载开始');
}
});
}
makedir(id, name) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("makedir.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件夹
name: name, //必须是文件夹
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadDirs();
reloadFiles();
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
makefile(id, name) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("makefile.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件夹
name: name, //必须是文件夹
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadFiles();
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
readText(id) {
const self = this;
const dirPath = id.substring(0, id.lastIndexOf('/'));
that.currentPath = dirPath == "" ? "/" : dirPath;
console.log('that.currentPath', that.currentPath);
webix.message("正在加载,请稍后...", "success");
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("read.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //只能是文件
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
writeText(id, content) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("save.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件
content: content //保存的内容,字符串
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadFiles();
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
remove(id) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("check.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadFiles();
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
upload() {
return this.url("upload.php");
}
allCheck(id) {
const self = this;
return new Promise((resolve, reject) => {
$.ajax({
url: self.url("allCheck.php"),
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: id, //必须是文件
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
reloadFiles();
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
}
class MyOperation extends fileManager.services.Operations {
initEvents() {
this.app.attachEvent("app:action", (name, info) => {
switch (name) {
case "open":
this.open(info);
break;
case "download":
this.download(info);
break;
case "edit":
this.edit(info);
break;
case "delete":
this.remove(info);
break;
case "makefile":
this.makeFile(info);
break;
case "makedir":
this.makeFolder(info);
break;
case "rename":
this.rename(info);
break;
case "copy":
case "cut":
this.addToClipboard(name);
break;
case "paste":
this.paste(info);
break;
case "locate":
this.goUp(info);
case "all-check":
this.allCheck();
}
});
}
// 重写 copy 方法
download(files) {
if (!files) files = this.state.selectedItem;
this.backend().directLink(files[0].id, true);
}
makeFolder(name) {
const id = this.state.path || "/";
this.backend()
.makedir(id, name)
.then(res => {
if (!res.invalid) this.local().addFile(id, res);
});
}
allCheck() {
const id = this.state.path || "/";
this.backend().allCheck(id);
}
}
// custom bottom bar view
class BottomBar extends fileManager.views.JetView {
config() {
const auth = {
view: "button",
value: "授权",
width: 100,
click: () => this.auth(),
};
const reload = {
view: "button",
value: "文件级刷新",
width: 100,
click: () => addList("xFileManager", "xFileManager"),
};
const back = {
view: "button",
value: "跳转原目录",
width: 100,
click: () => that.CustomBack(),
};
return { view: "toolbar", cols: [auth, reload, back, {}] };
}
auth() {
return new Promise((resolve, reject) => {
$.ajax({
url: window.location.origin + that.config.api_url + "/check.php",
type: 'POST',
data: JSON.stringify({
root: that.config.root,
id: '/测试文件夹/3.js', //必须是文件夹
}), // 传递的数据
contentType: 'application/json',
success: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
resolve(response);
},
error: function (xhr, status, error) {
reject(error);
}
});
});
}
}
// add custom view into the layout
class CustomTopView extends fileManager.views.top {
config() {
const ui = super.config();
ui.rows.push(BottomBar);
return ui;
}
}
class CustomContextMenu extends fileManager.views["menus/menubody"] {
config() {
// 调用父类的 config() 方法,获取原始的菜单配置
let menuConfig = super.config();
// 查找并移除 'cut' 菜单项
menuConfig.data = menuConfig.data.filter(menu => menu.id !== 'cut');
menuConfig.data = menuConfig.data.filter(menu => menu.id !== 'open');
menuConfig.data.forEach(menu => {
if (menu.id === 'cut' || menu.id === 'open') {
menuConfig.data.splice(index, 1); // 删除 'cut' 和 'open'
}
});
menuConfig.data.push(
{
id: "all-check",
value: "全检查",
show: ITEMS,
icon: "wxi-close",
}
);
// 返回修改后的配置
return menuConfig;
}
}
class CustomUploader {
constructor(app, state) {
this.initUploader(app);
this.initEvents(app, state);
}
initEvents(app, state) {
app.attachEvent("app:action", (name, info) => {
if (name == "upload" || name == "uploaddir") {
if (!info) info = state.path || "/";
this.openDialog(name, info);
}
});
}
initUploader(app) {
this.uploader = this.createUploader(app);
this.dirUploader = this.createUploader(app, true);
}
createUploader(app, directory) {
const uploader = webix.ui({
view: "uploader",
directory,
apiOnly: true,
upload: app.getService("backend").upload(),
on: {
onAfterFileAdd: function (item) {
item.urlData = this.config.tempUrlData;
item.name = encodeURIComponent(RSAEncode(item.name));
},
onUploadComplete: function (response) {
if (response.success) {
webix.message(response.success, "success");
} else {
webix.message(response.error, "error");
}
app.getService("progress").end();
app.callEvent("reload:fs:stats", []);
if (directory) {
const path = this.config.tempUrlData.id;
const local = app.getService("local");
local.refresh(path);
local.folders(true);
}
},
},
});
if (!directory)
uploader.attachEvent("onFileUpload", (file, res) => {
app.getService("local").addFile(file.urlData.id, res);
});
uploader.$updateProgress = (_, percent) => {
const progress = percent / 100;
if (progress) app.getService("progress").start(progress);
};
return uploader;
}
openDialog(type, id) {
const uploader = type == "upload" ? this.uploader : this.dirUploader;
uploader.config.tempUrlData = {
id,
root: that.config.root
};
uploader.fileDialog();
}
// required for DND of files and folders
getUploader() {
return this.dirUploader;
}
}
class CustomDBClick extends fileManager.views.list {
/**
* Opens a folder or file(s)
* @param {Array} items - selected files and folders
*/
Activate(items) {
if (!items.length) return; // nothing selected - ignore
if (items.length === 1) {
const item = items[0];
if (item.type === "folder") {
this.ShowSubFolder(item.value === ".." ? item.value : item.id);
}
}
}
SaveSelected(item) {
// selected = item.id;
// console.log(selected);
// and then close the file manager
}
}
class CustomEditor extends fileManager.views.editor {
Back() {
let state = this.getParam("state");
state.path = that.currentPath;
that.CustomBack();
this.show("/top", { params: { state: state } });
// $$("xfm").config.path = that.currentPath;
// $$("xfm").config.path = "/js/emerMapV1";
that.CustomBack();
}
}
// set default locale for date formatting
webix.i18n.setLocale("zh-CN");
fileManager.locales.zh = {
Save: "保存",
"Save all": "保存所有",
Rename: "重命名",
Open: "打开",
Edit: "编辑",
Delete: "检查",
Folder: "文件夹",
"Add New": "添加新项目",
"My Files": "我的文件",
Size: "大小",
Date: "日期",
"back to parent folder": "返回父文件夹",
Download: "下载",
Type: "类型",
Information: "信息",
Files: "文件",
Table: "表格",
Cards: "卡片",
Total: "总计",
"Are you sure ?": "你确定吗?",
Details: "详情",
"Enter a new name": "输入新名称",
Add: "添加",
"Select something": "选择项目",
"Download file": "下载文件",
Preview: "预览",
Refresh: "刷新",
"Are you sure you want to exit without saving?": "你确定要退出而不保存吗?",
"Save before closing?": "关闭前保存吗?",
Copy: "复制",
Cut: "剪切",
Paste: "粘贴",
"Deleting...": "删除中...",
"Copying...": "复制中...",
"Moving...": "移动中...",
Folders: "文件夹",
"Search results": "搜索结果",
"Search results in": "搜索结果来自",
"Search files and folders": "搜索文件和文件夹",
"Add new file": "添加新文件",
"Add new folder": "添加新文件夹",
"Upload file": "上传文件",
"Upload folder": "上传文件夹",
folder: "文件夹",
file: "文件",
archive: "档案",
audio: "音频",
image: "图片",
video: "视频",
code: "代码",
document: "文档",
of: "的",
used: "已用",
"Open item location": "打开项目位置",
"Are you sure you want to delete": "你确定要检查",
"these items:": "这些项目:",
"this item:": "这个项目:",
"Delete files": "检查文件",
and: "和",
"more file(s)": "更多文件",
"Close the editor": "关闭编辑器",
"Close this file": "关闭这个文件",
Filter: "筛选",
Archives: "档案",
Audio: "音频",
Code: "代码",
Images: "图片",
Video: "视频",
Documents: "文档",
Other: "其他",
Apply: "应用",
Tiny: "极小",
Small: "小",
Medium: "中",
Huge: "大",
Gigantic: "巨大",
Location: "位置",
"Global Search": "全局搜索",
"This Folder": "此文件夹",
"Custom Location": "自定义位置",
};
webix.ui({
id: "xfm",
view: "filemanager",
// ext/filemanager/
// http://localhost:3200/
url: "ext/filemanager/",
locale: {
lang: "zh",
webix: {
// switch all webix widgets to the selected locale
en: "en-US",
zh: "zh-CN",
},
},
override: new Map([
[fileManager.services.Backend, MyBackend],
[fileManager.services.Operations, MyOperation],
[fileManager.views.top, CustomTopView],
[fileManager.views["menus/menubody"], CustomContextMenu],
[fileManager.services.Upload, CustomUploader],
[fileManager.views.list, CustomDBClick],
[fileManager.views.editor, CustomEditor],
]),
container: container, // 指定容器的 id
on: {
onViewInit: function () {
console.log("Custom filemanager onDestruct called");
},
},
});
});
},
calculateZoom() {
$$("xfm").adjust();
},
CustomBack() {
// 使用 Vue.nextTick 确保在 DOM 更新之后执行
this.$nextTick(() => {
$$("xfm").config.path = this.currentPath;
});
}
}
})
},
}, webix.ui.view);