博客 (15)

要将一个服务公开给外部访问,必须开放端口,例举一些场景的端口映射:

  • VMware 实现宿主机端口映射到虚拟机端口:

    打开 VMware 的菜单中的 编辑 - 虚拟网络编辑器,

    在弹出窗口中表格中的选中“NAT”项,点击下方的“NAT设置”(若无法操作需要先点击“更改设置”允许)

    在“NAT设置”中“添加”,填写主机端口和虚拟机端口、虚拟机IP地址。

    这样,宿主机就能访问到虚拟机中的服务了。

  • 在 Windows 11 的防火墙中允许端口:

    在“设置”或“控制面板”中打开防火墙,“高级设置”

    在“入站规则”中“新建规则”

    选择“端口”并填写,完成。

    这样,局域网的其它电脑就能访问到这台 Windows 的服务了。

  • 小米路由器:

    登录路由器,进入“高级设置”,

    切换到“端口转发”,添加规则,

    这样,外网就能通过路由器的外网IP来访问局域网中的内网服务了。

  • 华为路由器 AX3 Pro:

    登录路由器,进入“更多功能”,

    展开“安全设置”,选择“NAT服务”,

    点击“+”号设置端口映射。

    这样,外网就能通过路由器的外网IP来访问局域网中的内网服务了。

  • H3C ER3200G2 路由器:

    登录路由器,展开“高级设置”,“地址转换”,

    切换到“虚拟服务器”,“新增”,

    填端口和内部服务器IP,完成。

    这样,外网就能通过路由器的外网IP来访问局域网中的内网服务了。

xoyozo 3 个月前
363

可能的原因是 IIS 安装了“WebDAV 发布”,在 IIS - 模块 中可见 WebDAVModule

解决方法:在网站的 web.config 中设置:

<system.webServer>
    <modules>
        <remove name="WebDAVModule" />
    </modules>
</system.webServer>

或在 IIS 中,在该网站的模块中删除 WebDAVModule。

如果网站需要用到 WebDAV 模块,那么考虑使用 Fetch API 或 XMLHttpRequest 来发送请求。


xoyozo 1 年前
1,068

在使用 VM 的过程中,你可能遇到过虚拟机和宿主机之间需要传输文件的问题。使用共享文件夹的方式比直接复制粘贴更快速、更有效,并且可以共同编辑文件,无需拷贝文件。如果在虚拟中有下载大文件,那么可以直接下载到共享文件夹中,可有效节省虚拟机磁盘文件 .vmdk 的空间占用(虚拟机里彻底删除文件后,.vmdk 并不会变小)。

设置方法很简单:

首先在宿主机磁盘上新建一个文件夹用来共享给虚拟机,例如取名 VM_SharedFolder。若无特殊需求,尽量不要将重要文件夹共享给虚拟机。

在 VM 中选中要共享文件夹的虚拟机,右键,设置。切换到“选项”选项卡,左侧选中“共享文件夹”,右侧选择“总是启用”,勾选“在 Windows 客户机中映射为网络驱动器”。点击“添加”,选择上一步创建的文件夹,完成。

这样,在虚拟机的“此电脑”上就可以看到这个“网络位置”了,默认是 Z 盘。

以上步骤可以在虚拟机开机的时候进行设置。

老版迅雷不能把下载目录指定到这样的盘符下,可以指定到:\\vmware-host\Shared Folders\VM_SharedFolder

xoyozo 1 年前
1,441

不知道从哪个版本的 Chrome 或 Edge 开始,我们无法通过 ctrl+v 快捷键将时间格式的字符串粘贴到 type 为 date 的 input 框中,我们想办法用 JS 来实现。


方式一、监听 paste 事件:

const input = document.querySelector('input[type="date"]');
input.addEventListener('paste', (event) => {
    input.value = event.clipboardData.getData('text');
});

这段代码实现了从页面获取这个 input 元素,监听它的 paste 事件,然后将粘贴板的文本内容赋值给 input。

经测试,当焦点在“年”的位置时可以粘贴成功,但焦点在“月”或“日”上不会触发 paste 事件。


方式二、监听 keydown 事件:

const input = document.querySelector('input[type="date"]');
input.addEventListener('keydown', (event) => {
    if ((navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey) && event.key === 'v') {
        event.preventDefault();
        var clipboardData = (event.clipboardData || event.originalEvent.clipboardData);
        input.value = clipboardData.getData('text');
    }
});

测试发现报错误:

Uncaught TypeError: Cannot read properties of undefined (reading 'getData')

Uncaught TypeError: Cannot read properties of undefined (reading 'clipboardData')

看来 event 中没有 clipboardData 对象,改为从 window.navigator 获取:

const input = document.querySelector('input[type="date"]');
input.addEventListener('keydown', (event) => {
    if ((navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey) && event.key === 'v') {
        event.preventDefault();
        window.navigator.clipboard.readText().then(text => {
            input.value = text;
        });
    }
});

缺点是需要用户授权:

image.png

仅第一次需要授权,如果用户拒绝,那么以后就默认拒绝了。


以上两种方式各有优缺点,选择一种适合你的方案就行。接下来继续完善。


兼容更多时间格式,并调整时区

<input type="date" /> 默认的日期格式是 yyyy-MM-dd,如果要兼容 yyyy-M-d 等格式,那么:

const parsedDate = new Date(text);
if (!isNaN(parsedDate.getTime())) {
    input.value = parsedDate.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' }).split('/').reverse().join('-');
}

以 text 为“2023-4-20”举例,先转为 Date,如果成功,再转为英国时间格式“20-04-2023”,以“/”分隔,逆序,再以“-”连接,就变成了“2023-04-20”。

当然如果希望支持中文的年月日,可以先用正则表达式替换一下:

text = text.replace(/\s*(\d{4})\s*年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日\s*/, "$1-$2-$3");


处理页面上的所有 <input type="date" />

const inputs = document.querySelectorAll('input[type="date"]');
inputs.forEach((input) => {
    input.addEventListener(...);
});


封装为独立域

避免全局变量污染,使用 IIFE 函数表达式:

(function() {
  // 将代码放在这里
})();

或者封装为函数,在 jQuery 的 ready 中,或 Vue 的 mounted 中调用。


在 Vue 中使用

如果将粘贴板的值直接赋值到 input.value,在 Vue 中是不能同步更新 v-model 绑定的变量的,所以需要直接赋值给变量:

<div id="app">
    <input type="date" v-model="a" data-model="a" v-on:paste="fn_pasteToDateInput" />
    {{a}}
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const app = Vue.createApp({
        data: function () {
            return {
                a: null,
            }
        },
        methods: {
            fn_pasteToDateInput: function (event) {
                const text = event.clipboardData.getData('text');
                const parsedDate = new Date(text);
                if (!isNaN(parsedDate.getTime())) {
                    const att = event.target.getAttribute('data-model');
                    this[att] = parsedDate.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' }).split('/').reverse().join('-');
                }
            },
        }
    });
    const vm = app.mount('#app');
</script>

示例中 <input /> 添加了 data- 属性,值同 v-model,并使用 getAttribute() 获取,利用 this 对象的属性名赋值。 

如果你的 a 中还有嵌套对象 b,那么 data- 属性填写 a.b,方法中以“.”分割逐级查找对象并赋值

let atts = att.split('.');
let target = this;
for (let i = 0; i < atts.length - 1; i++) {
    target = target[atts[i]];
}
this.$set(target, atts[atts.length - 1], text);


xoyozo 2 年前
1,363

众所周知,VMware Workstation 中分配的硬盘大小与实际的 .vmdk 文件尺寸是不一致的。

譬如为虚拟机创建了一块 60GB 的硬盘,安装完系统后使用了 20GB 的空间,此时在宿主机上查看到的 .vmdk 的大小是 20GB。


当虚拟机的磁盘空间不足时,可以在 VMware 中扩展容量或添加硬盘。

扩展容量:虚拟机 - 设置 - 硬件 - 硬盘 - 扩展 - 设置最大磁盘大小。譬如设置为 160GB,那么虚拟机的磁盘 0 的大小会变成 160GB,C 盘 60GB 不变,在磁盘管理中可以将未分配的 100GB 创建为 D 盘。

添加硬盘:虚拟机 - 设置 - 硬件 - 添加 - 硬盘 - 设置最大磁盘大小。譬如设置为 100GB,那么虚拟机会增加一块大小为 100GB 的磁盘 1,在磁盘管理中可以将它创建为 100GB 的 D 盘。


当虚拟机内删除文件后,观察到 .vmdk 文件的大小并没有相应地减小,网上搜索后找到很多办法,但未测试成功。索性删除并重装系统比较方便,如果虚拟机里安装了许多软件不方便重装的朋友继续研究。

xoyozo 2 年前
2,950

一般我们使用原生 JS 的时候使用 abort() 来取消 fetch 请求。

在使用 axios 发送请求时,如果我们想要取消请求,可以使用 axios 提供的 CancelToken 和 cancel 方法。下面是具体的实现步骤:

// 创建 CacnelToken 实例
const cancelTokenSource = axios.CancelToken.source();
// GET 方式请求
axios.get(url, {
    cancelToken: cancelTokenSource.token
}).catch(thrown => {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    console.log('An error occurred', thrown);
  }
});
// POST 方式请求
axios.post(url, data, {
    cancelToken: cancelTokenSource.token
});
// 取消请求
cancelTokenSource.cancel('请求被取消');

get 请求的时候,cancelToken 是放在第二个参数里;post 的时候,cancelToken 是放在第三个参数里。axios-0.27.2 中测试成功。

在即时响应的搜索框中可以这样处理:(vue3)

let cancelTokenSource;
const app = Vue.createApp({
    methods: {
        fn_list: function () {
            // 如果已有请求则取消
            cancelTokenSource && cancelTokenSource.cancel();
            // 创建一个新的请求
            cancelTokenSource = axios.CancelToken.source();
            axios.post(url, data, {
                cancelToken: cancelTokenSource.token
            }).then(function (response) {
                // 请求成功
            }).catch(function (error) {
                // 请求失败/取消
            });
        },
    }
});
const vm = app.mount('#app');


如果你用 jQuery,请参此文

xoyozo 2 年前
2,395

image.png

“安装 VMware Tools”灰色无法点击。

image.png

打开虚拟机设置 - CD/DVD - 使用 ISO 映像文件,浏览到 VM 安装目录,根据对应操作系统选择镜像即可(如 linux.iso、windows.iso)

image.png

这样,虚拟机的计算机界面就会出现 VMware Tools 的安装包。

xoyozo 2 年前
3,473

本文以 vue 3 为例,vue 2 同理。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>clipboard.js 在 vue.js 中使用</title>
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="item in list">
                {{item}}
                <button class="btn-cp" :value="item">复制</button>
            </li>
        </ul>
    </div>
    <script src="//xxx/vue/core-3.x.x/vue.global.js"></script>
    <script src="//xxx/clipboard/clipboard.js-2.x.x/dist/clipboard.min.js"></script>
    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    list: ["aaa", "bbb", "ccc"]
                }
            },
            mounted: function () {
                var clipboard = new ClipboardJS('.btn-cp', {
                    text: function (trigger) {
                        return trigger.getAttribute('value');
                    }
                });
                clipboard.on('success', function (e) {
                    alert('已复制“' + e.text + '”到粘贴板');
                    e.clearSelection();
                });
            }
        });
        const vm = app.mount('#app');
    </script>
</body>
</html>


xoyozo 2 年前
1,915

使用 JS 根据屏幕宽度来计算页面尺寸和字体大小并不是一个好办法。

rem 单位是跟随网页根元素的 font-size 改变而自动改变的,是一个相对的长度单位,非常适合在不同手机界面上自适应屏幕大小。 


一般的手机浏览器的默认字体大小为 16px。即:

:root { font-size: 16px; } /* 在 HTML 网页中,:root 选择器相当于 html */

也就是说,如果我定义一个宽度为 1rem,那么它的实际宽度为 16px。反过来说,如果设计稿宽度为 1000px,那么相当于 1000÷16=62.5‬rem。因此,我们设置一个 62.5rem 的宽度,即可正常显示 1000px 的设计效果。


为了适应不同屏幕尺寸的手机,只需按比例换算更改 :root 的 font-size 即可。这个比例是由“窗口宽度 ÷ 设计稿宽度”得到的,姑且称它为“窗设比”。

以 iPhone X 逻辑宽度 375px 为例,设计稿宽度 1000px 时,窗设比为:0.375。:root 的 font-size 将会被重置为 16 * 0.375 = 6px。上面的 62.5rem 宽度将对应 iPhone X 宽度为 62.5 * 6 = 375px,正好是屏幕逻辑宽度。


根据这个思路,理论上,我们只需要使用 CSS 将 :root 的 font-size 设置为 1px,然后使用 JS 重置为与“窗设比”相乘的积(本例中将被重置为 0.375px)。这样,我们可以不通过换算,只需要更改单位,将设计稿中的 500px 直接写入到 CSS 为 500rem,即可实现在 iPhone X 上显示逻辑宽度为(500 * 0.375 =)187.5px,即设计稿中的 1/2 宽度对应到屏幕上 1/2 的宽度。


实际上,部分安卓手机(如华为某旗舰机)不支持 :root 的 font-size 小于 12px,其它一些浏览器也会出现小于 12px 的文字以 12px 显示。


为了避免在任何环节出现小于 12px 的 font-size 值,且不增加设计稿尺寸到 CSS 数值的换算难度,我们设计了以下思路:

  1. 约定“设样比(设计稿的 px 值与 CSS 的 rem 值的比)”为 100;

  2. 使用 JS 将 :root 的 font-size 重置为“设样比”与“窗设比(窗口宽度 ÷ 设计稿宽度)”的乘积;

  3. 计算 CSS 时的 rem 值只需从设计稿中获取 px 值再除以“设样比”100 即可。 


这样做的另一个好处是,不用要求设计师必须按多少尺寸来设计,当然按主流尺寸来建议是可行的。


完整代码(jQuery 版):

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app { margin: 0 auto; background-color: #EEE; display: none; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app">
        <div class="whole">100%</div>
        <div class="half">50%</div>
        <div class="half2">50%</div>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script>
        // [函数] 重置尺寸
        function fn_resize() {
            // 设计稿宽度(px)
            var designsWidth = 1000;
            // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
            var px2rem = 100;
            // 限制 #app 的最大宽度为设计稿宽度
            $('#app').css('max-width', designsWidth).css('min-height', $(window).height());
            // 重置 :root 的 font-size
            var appWidth = Math.min($(window).width(), designsWidth);
            $(':root').css('font-size', px2rem * appWidth / designsWidth);
            $('#app').show();
        }
        // 页面加载完成后重置尺寸
        $(fn_resize);
        // 改变窗口大小时重置尺寸
        $(window).resize(fn_resize);
    </script>
</body>
</html>

完整代码(Vue2 版):

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app { margin: 0 auto; background-color: #EEE; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px' }" style="display: none;" v-show="appShow">
        <div class="whole">100%</div>
        <div class="half">50%</div>
        <div class="half2">50%</div>
        <div>appWidth: {{ appWidth }}</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                // 设计稿宽度(px)
                designsWidth: 1000,
                // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
                px2rem: 100,
                // #app 的 width
                appWidth: 0, // 勿改
                appMinHeight: 0, // 勿改
                appShow: false, // 勿改
            },
            mounted: function () {
                // 页面加载完成后重置尺寸
                this.fn_resize();
                const that = this;
                // 改变窗口大小时重置尺寸
                window.onresize = () => {
                    return (() => {
                        console.log('RUN window.onresize()');
                        that.fn_resize();
                    })();
                };
            },
            watch: {
                // 侦听 appWidth 更改 root 的 font-size
                appWidth: function () {
                    console.log('RUN watch: appWidth');
                    var root = document.getElementsByTagName("html")[0];
                    root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
                    this.appShow = true;
                }
            },
            methods: {
                fn_resize: function () {
                    console.log('RUN methods: fn_resize()');
                    this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
                    this.appMinHeight = document.documentElement.clientHeight;
                }
            }
        });
    </script>
</body>
</html>

完整代码(Vue3 版):

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
    <title>Index</title>
    <style>
        body { margin: 0; text-align: center; }
        #app > div { display: none; margin: 0 auto; background-color: #EEE; }
        /* 长度计算方法:从设计稿获得长度(例如 160px),除以 px2rem 值(本例为 100),得到 1.6rem */
        .whole { background-color: #D84C40; width: 10rem; }
        .half { background-color: #3296FA; width: 5rem; }
        .half2 { background-color: #3296FA; width: 5rem; margin-left: 5rem; }
    </style>
</head>
<body>
    <div id="app">
        <div style="display: none;" v-bind:style="{ maxWidth: designsWidth + 'px', minHeight: appMinHeight + 'px', display: appShow ? 'block' : 'none' }">
            <div class="whole">100%</div>
            <div class="half">50%</div>
            <div class="half2">50%</div>
            <div>appWidth: {{ appWidth }}</div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
    <script>
        const app = Vue.createApp({
            data: function () {
                return {
                    // 设计稿宽度(px)
                    designsWidth: 1000,
                    // 约定的“设计稿 px 值与 CSS rem 值的比”,为方便计算,一般无需改动
                    px2rem: 100,
                    // #app 的 width
                    appWidth: 0, // 勿改
                    appMinHeight: 0, // 勿改
                    appShow: false, // 勿改
                }
            },
            mounted: function () {
                // 页面加载完成后重置尺寸
                this.fn_resize();
                const that = this;
                // 改变窗口大小时重置尺寸
                window.onresize = () => {
                    return (() => {
                        console.log('RUN window.onresize()');
                        that.fn_resize();
                    })();
                };
            },
            watch: {
                // 侦听 appWidth 更改 root 的 font-size
                appWidth: function () {
                    console.log('RUN watch: appWidth');
                    var root = document.getElementsByTagName("html")[0];
                    root.style.fontSize = (this.px2rem * this.appWidth / this.designsWidth) + 'px';
                    this.appShow = true;
                }
            },
            methods: {
                fn_resize: function () {
                    console.log('RUN methods: fn_resize()');
                    this.appWidth = Math.min(document.body.clientWidth, this.designsWidth);
                    this.appMinHeight = document.documentElement.clientHeight;
                }
            }
        });
        const vm = app.mount('#app');
    </script>
</body>
</html>


示例中 CSS 初始 :root 的 font-size 为 16px(一个较小值,防止页面加载时瞬间出现大号文字影响用户体验),经过 fn_resize 后,:root 的 font-size 被设置为(100 * 375 / 1000 =)37.5px(iPhone X 中),那么宽度为 1000px 的设计稿中的 500px 换算到 CSS 中为 5rem,也即 37.5 * 5 = 187.5px,就是 iPhone X 的屏幕宽度的一半。

示例中 id 为 app 的 div 是用来在 PC 浏览器中限制页面内容最大宽度的(类似微信公众号发布的文章),如果网页不需要在 PC 端显示,jQuery 版代码中的 $('#app').width() 可以用 $(window).width() 来代替。

这个 div#app 一般设计有背景色,用于在宽度超过设计稿的设备上显示时区别于 body 的背景。但是当网页内容不超过一屏时,div#app 高度小于窗口,示例中与 appMinHeight 相关的代码就是为了解决这个问题。

示例中 div#app 隐藏/显示相关代码用于解决在页面加载初期由于 font-size 值变化引起的一闪而过的排版错乱。

image.png


最后补充一点,如果改变窗口大小时涉及执行耗时的操作,为避免页面卡顿,可以参考这篇文章添加函数防抖:https://xoyozo.net/Blog/Details/js-function-anti-shake

xoyozo 5 年前
5,993

打开配置文件:

vi /etc/sysconfig/network-scripts/ifcfg-eth0

将 ONBOOT 改成 yes 即可:

ONBOOT=yes

重启网络使配置生效:

service network restart

/etc/init.d/network restart


详细安装 VMware Tools 官方教程:在 Linux 虚拟机中手动安装 VMware Tools

xoyozo 6 年前
3,956