Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
載說明:原創不易,未經授權,謝絕任何形式的轉載
模態框(彈出層對話框)在大多數現代應用程序中非常常見。它們主要用于呈現簡潔的信息,非常適合顯示廣告和促銷內容。模態框提供了一種快速傳達信息的方式,并提供了用戶友好的關閉選項。
在本文中,我們將使用Vuejs構建一個彈出模態框。該模態框將包括一個取消或關閉按鈕,以方便用戶在完成任務后關閉它。此外,我們還將實現一個功能,允許用戶在模態框區域外點擊以關閉它。
<script setup lang="ts">
import { ref } from 'vue';
const message = ref('This is a modal popup');
const emit = defineEmits(['close']);
const closeModal = () => {
emit('close');
};
</script>
<template>
<div class="popup" @click.self="closeModal">
<div class="popup-content">
<div class="popup-header">
<h2 class="popup-title">{{ message }}</h2>
<button class="popup-close-button" @click.prevent="closeModal">X</button>
</div>
<article>
<div class="popup-content-text">
This is a simple modal popup in Vue.js
</div>
</article>
</div>
</div>
</template>
Script Section
<script setup lang="ts">
import { ref } from 'vue';
const message = ref('This is a modal popup');
const emit = defineEmits(['close']);
const closeModal = () => {
emit('close');
};
</script>
在這個部分,我們從Vue中導入所需的功能。
Template Section
<template>
<div class="popup" @click.self="closeModal">
<div class="popup-content">
<div class="popup-header">
<h2 class="popup-title">{{ message }}</h2>
<button class="popup-close-button" @click.prevent="closeModal">X</button>
</div>
<article>
<div class="popup-content-text">
This is a simple modal popup in Vue.js
</div>
</article>
</div>
</div>
</template>
本段代碼義了模板中模態框的結構。
<script setup lang="ts">
import { ref } from 'vue'
import Popup from "@/components/Popup.vue"; // @ is an alias to /src
const msg = ref('Hello World!')
const isOpened = ref(false)
</script>
<template>
<div>
<h1>{{ msg }}</h1>
<button @click="isOpened = !isOpened">Open Popup</button>
<Teleport to="body">
<Popup v-if="isOpened" @close="isOpened = !isOpened" />
</Teleport>
</div>
</template>
數據和狀態管理:
代碼使用Vue的ref函數創建了兩個響應式變量:
按鈕點擊事件
模板中有一個帶有點擊事件監聽器(@click)的<button>元素。當按鈕被點擊時,它會切換isOpened變量的值,從而有效地打開或關閉彈出窗口。
導入彈出框組件
組件之間的通信:
您可以在CodeSandbox上使用本文中設計的代碼進行操作。
https://codesandbox.io/s/suspicious-kepler-993dmh?file=%2Fsrc%2Fviews%2FHome.vue%3A0-420
由于文章內容篇幅有限,今天的內容就分享到這里,文章結尾,我想提醒您,文章的創作不易,如果您喜歡我的分享,請別忘了點贊和轉發,讓更多有需要的人看到。同時,如果您想獲取更多前端技術的知識,歡迎關注我,您的支持將是我分享最大的動力。我會持續輸出更多內容,敬請期待。
作為 TOB 的業務方,我們偶爾會收到一些如下圖所示的反饋。
作為 PC 頁面為主的業務方,大多數用戶在一天的工作中,可能都不太會刷新或者重新打開我們的頁面,導致我們在下午或者白天發布的前端版本,往往需要到幾個小時甚至第二天,才能覆蓋到 98% 以上的用戶。
我們統計了 bscm 平臺 5 次下午 2-3 點左右發布的版本,在發布后每個時間段內老版本用戶的占比情況。選擇這個時間點發布的原因是這個時間點基本是平臺用戶的上班時間,是最有可能出現用戶已經打開了頁面同時我們在發布新代碼的場景的,比較具有代表性。按平臺用戶六七點下班來看,我們可以看到還有將近 6% 的用戶在當天是會一直訪問老版本的前端代碼的,按照 bscm 平臺 1w+的 uv 來看,約有 600 多人會可能遇到前端版本過低導致的使用問題。
首先介紹兩個概念,本地版本號和云端版本號。本地版本號是用戶請求到的前端頁面的代碼版本號,是用戶訪問頁面時決定;云端版本號可以理解為最新前端版本號,它是每次開發者發布前端代碼時決定的。
有了彈窗的觸發條件,我們還需要去決定什么時候判斷彈窗是否滿足觸發的條件,上面也提到了,出現這類問題的場景多見于用戶在使用過程中,開發者進行了前端代碼發布,那我們主要可以有兩個類型的時機去進行觸發條件的判斷。
我們對這些時機在更新是否及時,判斷次數多少、實現成本高低等維度進行一個對比。
?? 越多表示這個維度得分越高
根據表格可以看到 websocket 消息推送和前端事件監聽這兩種方案綜合來看是更合適一些的,但是前端事件監聽其實它的劣勢在實際運用場景中會被弱化(一天的上線數量有限,請求次數一天不會多太多次),但是實現成本遠低于 websocket,所以無疑是實際落地場景中比較理想的選擇。
根據 can i use 的結果我們也可以發現 visibilitychange 事件也基本符合我們目前 B 端頁面對于 PC 瀏覽器的要求。
本地版本號
本地版本號是用戶訪問時決定的,那無疑頁面的 html 文件就是這個版本號存在的最佳載體,我們可以在打包時通過 plugin 給 html 文件注入一個版本號。
云端版本號
云端版本號的選擇則有很多方式了,數據庫、cdn 等等都可以滿足需求。不過考慮到實現成本和泳道的情況,想了一下兩個思路一個是打包的同時生成一個 version.json 文件,配一個路由去訪問;另一個是直接訪問對應的 html 代碼,解析出注入的版本號,二者各自有適合的場景。
我們現在的大多數項目都包含了主應用和子應用,那其實不管是子應用的更新還是主應用的更新都應該有相關的提示,而且相互獨立,但同時又需要保證彈窗不同時出現。
想要沿用之前的方案其實只需要解決三個問題。
正如前文提到的,本身版本發布不是一個高頻事件,但是監聽事件的頻次有時候可能過高了,不希望頻繁的去進行觸發條件判斷。同時如果出現一天內多次發布的場景,也不希望這個彈窗對于用戶有過多的打擾,所以需要去添加一個頻控邏輯。
plugin
/* eslint-disable */
import { CoraWebpackPlugin, WebpackCompiler } from '@ies/eden-web-build';
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');
interface IVersion {
name?: string; // 編譯完的文件夾名稱
subName?: string; // 子應用的名稱,主應用可以不傳
}
export class VersionPlugin implements CoraWebpackPlugin {
readonly name = 'versionPlugin'; // 插件必須要有一個名字,這個名字不能和已有插件沖突
private _version: number;
private _name: string;
private _subName: string;
constructor(params: IVersion) {
this._version = new Date().getTime();
this._name = params?.name || 'build';
this._subName = params?.subName || ''
}
apply(compiler: WebpackCompiler): void {
compiler.hooks.afterCompile.tap('versionPlugin', () => {
try {
const filePath = path.resolve(`./${this._name}/template/version.json`);
fs.writeFile(filePath, JSON.stringify({ version: this._version }), (err: any) => {
if (err) {
console.log('@@@err', err);
}
});
const htmlPath = path.resolve(`./${this._name}/template/index.html`);
const data = fs.readFileSync(htmlPath);
const $ = cheerio.load(data);
$('body').append(`<div id="${this._subName}versionTag" style="display: none">${this._version}</div>`);
fs.writeFile(htmlPath, $.html(), (err: any) => {
if (err) {
console.log('@@@htmlerr', err);
}
});
} catch (err) {
console.log(err);
}
});
}
}
彈窗組件
import React, { useEffect } from 'react';
import { Modal } from '@ecom/auxo';
import axios from 'axios';
import moment from 'moment';
export interface IProps {
isSub?: boolean; // 是否為子應用
subName?: string; // 子應用名稱
resourceUrl?: string; // 子應用的資源url
}
export type IType = 'visibilitychange' | 'popstate' | 'init';
export default React.memo<IProps>(props => {
const { isSub = false, subName = '', resourceUrl = '' } = props || {};
const cb = (latestVersion: number | undefined, currentVersion: number | undefined, type: IType) => {
try {
// 版本落后,提示可以刷新頁面
if (latestVersion && currentVersion && latestVersion > currentVersion) {
// 提醒過了就設置一個更新提示過期時間,一天內不需要再提示了,彈窗過期時間暫時全局只需要一個??!
localStorage.setItem(`versionUpdateExpireTime`, moment().endOf('day').format('x'));
if (!document.getElementById('versionModalTitle')) {
Modal.confirm({
title: <div id="versionModalTitle">版本更新提示</div>,
content:
'您已經長時間未使用此頁面,在此期間平臺有過更新,如您此時在頁面中沒有填寫相關信息等操作,請點擊刷新頁面使用最新版本!',
okText: <div data-text={`前端版本升級引導-立即更新 ${type}`}>刷新頁面</div>,
cancelText: <div data-text={`前端版本升級引導-我知道了 ${type}`}>我知道了</div>,
onCancel: () => {
console.log('fe-version-watcher INFO: 未更新~');
},
onOk: () => {
location.reload();
},
});
}
}
// 不管版本是否落后,半小時內都不需要去重新請求判斷
localStorage.setItem(`versionInfoExpireTime`, String(new Date().getTime() + 1000 * 60 * 30));
} catch {}
};
const formatVersion = (text?: string) => (text ? Number(text) : undefined);
useEffect(() => {
try {
const fn = function (type: IType) {
if (document.visibilityState === 'visible') {
/**
* @desc 為了防止打擾,版本更新每個應用一天只提示一次 所以過期時間設為當天23:59:59,沒過期則直接return
*/
if (Number(localStorage.getItem(`versionUpdateExpireTime`) || 0) >= new Date().getTime()) {
return;
}
/**
* @desc 不需要每次切換頁面都去判斷資源,每次從服務器獲取到的版本信息,給半個小時的緩存時間,需要區分子應用
*/
if (Number(localStorage.getItem(`versionInfoExpireTime`) || 0) > new Date().getTime()) {
return;
}
if (!isSub) {
/**
* @desc 主應用使用version.json文件來獲取最新的版本號
*/
const dom = document.getElementById('versionTag');
const currentVersion = formatVersion(dom?.innerText);
axios.get(`/version?timestamp=${new Date().getTime()}`).then(res => {
const latestVersion = res?.data?.version;
cb(latestVersion, currentVersion, type);
});
} else {
/**
* @desc 子應用使用最新html中的innerText來獲取最新版本號
*/
if (resourceUrl) {
const dom = document.getElementById(`${subName}versionTag`);
const currentVersion = dom?.innerText ? Number(dom?.innerText) : undefined;
axios.get(resourceUrl).then(res => {
/** ignore_security_alert */
try {
const html = res.data;
const doc = new DOMParser().parseFromString(html, 'text/html');
const latestVersion = formatVersion(doc.getElementById(`${subName}versionTag`)?.innerText);
cb(latestVersion, currentVersion, type);
} catch {}
});
}
}
}
};
const visibleFn = () => {
fn('visibilitychange');
};
const routerFn = () => {
fn('popstate');
};
if (isSub) {
// 子應用可能會有緩存,初始化的時候先判斷一次
fn('init');
}
document.addEventListener('visibilitychange', visibleFn);
window.addEventListener('popstate', routerFn);
return () => {
document.removeEventListener('visibilitychange', visibleFn);
window.removeEventListener('popstate', routerFn);
};
} catch {}
}, []);
return <div />;
});
npm i @ecom/fe-version-watcher-plugin # 安裝plugin
npm i @ecom/logistics-supply-chain-fe-version-watcher # 安裝引導彈窗
import { VersionPlugin } from '@ecom/fe-version-watcher-plugin';
// 有些項目打包后template文件夾下的名字不是build而是build_cn
// 可以根據自己項目的實際情況傳入{name: build_cn}
{
...,
plugins: [
...,
[VersionPlugin, {}],
]
}
import { FeVersionWatcher } from '@ecom/logistics-supply-chain-fe-version-watcher';
<FeVersionWatcher />
采用 version.json 的方案,引入 FersionWatcher 組件就不再需要任何參數,目前主應用只支持這種模式。未來也將參考子應用,主應用支持讀取 html 中版本標識的能力,將配置路由的工作改成組件 props 傳入資源 url,開發者可以根據實際情況自行選擇。
npm i @ecom/fe-version-watcher-plugin # 安裝plugin
npm i @ecom/logistics-supply-chain-fe-version-watcher # 安裝引導彈窗
import { VersionPlugin } from '@ecom/fe-version-watcher-plugin';
// 有些項目打包后template文件夾下的名字不是build而是build_cn
// 可以根據自己項目的實際情況傳入{name: build_cn}
{
...,
plugins: [
...,
[VersionPlugin, {subName: 'general-supplier', name: 'build_cn'}],
]
}
import { FeVersionWatcher } from '@ecom/logistics-supply-chain-fe-version-watcher';
// subName需要和plugin的參數保持一致,resourceUrl為子應用資源的路徑(子引用goofy上配置的路由)
<FeVersionWatcher isSub subName="general-supplier" resourceUrl="/webApp/general-supplier" />
resourceUrl一般就是goofy上配置的路由設置,,如果不同平臺有區分,可以動態傳入。
發布成功后,可以根據如下步驟測試:
同樣我們截取了 4 次該平臺 2-3 點發布的版本情況,可以看到老版本用戶的 uv 占比有著明顯的下降。
上線至今共計提示 10 萬+用戶,幫助約 5 萬人次及時更新了前端代碼。
作者:費昀鋒
來源:微信公眾號:字節前端 ByteFE
出處:https://mp.weixin.qq.com/s/PT0PZ3S1Cvh2nltcIKwa3g
生javascript實現帶動畫的提示型彈窗,常用于網站彈層的彈窗也有很多,一般用插件比較多,所以今天就來寫一寫該功能,如有錯誤之處請指出!
彈出跟消失都有放大縮小動畫在里面!
實現方法:
html:
可以自己輸入內容,再點擊彈出即可看到彈窗效果
css:
javascript:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。