主頁 > 企業開發 > 前端微服務無界實踐

前端微服務無界實踐

2023-05-26 12:43:27 企業開發

一、前言

隨著專案的發展,前端SPA應用的規模不斷加大、業務代碼耦合、編譯慢,導致日常的維護難度日益增加,同時前端技術的發展迅猛,導致功能擴展吃力,重構成本高,穩定性低,因此前端微服務應運而生,

前端微服務優勢

1.復雜度可控: 業務模塊解耦,避免代碼過大,保持較低的復雜度,便于維護與開發效率,

2.獨立部署: 模塊部署,減少模塊影響范圍,單個模塊發生錯誤,不影響全域,提升專案穩定性,

3.技術選型靈活: 在同一專案下可以使用市面上所有前端技術堆疊,也包括未來的前端技術堆疊,

4.擴展性,提升業務動態擴展的可能,避免資源浪費

微前端服務結構

技術對比和選型:

選型 靜態資源預加載 子應用保活 iframe js沙箱 css沙箱 接入成本 地址
EMP × × × https://github.com/efoxTeam/emp
Qiankun × × 中低 https://qiankun.umijs.org/zh/
無界 中低 https://wujie-micro.github.io/doc/
micro-app × × 中低 https://zeroing.jd.com/micro-app/

通過對比多種技術對專案的支持情況和專案接入的成本,我們最終選型無界,

二、wujie簡單用法(以主應用使用vue框架為例)

主應用是vue框架可直接使用wujie-vue,react框架可直接使用wujie-react,先安裝對應的插件哦

主應用改造:

// 引入無界,根據框架不同版本不同,引入不同的版本
import { setupApp, bus, preloadApp, startApp } from 'wujie-vue2'

// 設定子應用默認引數
setupApp({
    name: '子應用id(唯一值)',
    url: "子應用地址",
    exec: true,
    el: "容器",
    sync: true
})

// 預加載
preloadApp({ name: "唯一id"});

// 啟動子應用
startApp({ name: "唯一id"});

子應用改造:

1、跨域

子應用如果支持跨域,則不用修改

原因:存在請求子應用資源跨域

方案:因前端應用基本是前后端分離,使用proxy代理,只需配置在子應用配置允許跨域即可

// 本地配置
server: {
    host: '127.0.0.1', // 本地啟動如果主子應用沒處在同一個ip下,也存在跨域的問題,需要配置
    headers: {
        'Access-Control-Allow-Credentials': true,
        'Access-Control-Allow-Origin': '*', // 如資源沒有攜帶 cookie,需設定此屬性
        'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
        'Access-Control-Allow-Methods': '*'
    }
}

// nginx 配置
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Headers 'X-Requested-With,Content-Type';
add_header Access-Control-Allow-Methods "*";

2、運行模式選擇

無界有三種運行模式:單例模式、保活模式、重建模式

(1)、保活模式(長存頁面)

釋義:類似于vue的keep-alive性質(子應用實體和webcomponent不銷毀,狀態、路由都不丟失,只做熱webcomponent的熱插拔),子應用不想做生命周期改造,子應用切換又不想有白屏時間,可以采用保活模式,主應用上有多個入口跳轉到子應用的不同頁面,不能采用保活模式,因為無法改變子應用路由,

配置:只需要在主應用加載子應用的時候,配置引數添加alive:true

效果:預加載+保活模式=頁面資料請求和渲染提前完成,實作瞬間打開效果

(2)、單例模式

釋義:子應用頁面切走,會呼叫window.__WUJIE_UNMOUNT銷毀子應用當前實體,子應用頁面如果切換回來,會呼叫window.__WUJIE_MOUNT渲染子應用新的子應用實體,程序相當于:銷毀當前應用實體 => 同步新路由 => 創建新應用實體

配置:只需要在主應用加載子應用的時候,配置引數添加alive:false

改造生命周期

// window.__POWERED_BY_WUJIE__用來判斷子應用是否在無界的環境中
if (window.__POWERED_BY_WUJIE__) {
  let instance;
  // 將子應用的實體和路由進線創建和掛載
  window.__WUJIE_MOUNT = () => {
    const router = new VueRouter({ routes });
    instance = new Vue({ router, render: (h) => h(App) }).$mount("#app");
  };
   // 實體銷毀
  window.__WUJIE_UNMOUNT = () => {
    instance.$destroy();
  };
} else {
  // 子應用單獨啟動
  new Vue({ router: new VueRouter({ routes }), render: (h) => h(App) }).$mount("#app");
}
 

(3)、重建模式

釋義:每次頁面切換銷毀子應用webcomponent+js的iframe,

配置:只需要在主應用加載子應用的時候,配置引數添加alive:false

無生命周期改造

備注:非webpack打包的老專案,子應用切換可能出現白屏,應盡可能使用保活模式降低白屏時間

三、加載模塊(主應用配置)

子應用基礎資訊管理

// subList.js 資料可在配置頁面動態配置
const subList = [
    {
        "name":"subVueApp1",
        "exec":true,// false只會預加載子應用的資源,true時預執行子應用代碼
        "alive": true,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx1-pre.com",
            "gray":"http://xxx1-gray.com",
            "prod":"http://xxx1.com"
        }
    },
    {
        "name":"subVueApp2",
        "exec":false,// false只會預加載子應用的資源,true時預執行子應用代碼
        "alive": false,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx2-pre.com",
            "gray":"http://xxx2-gray.com",
            "prod":"http://xxx2.com"
        }
    }
]
export default subList;
// hostMap.js
import subList from './subList'

 const env = process.env.mode || 'pre'

// 子應用map結構
const subMap = {}
const subArr = []

// 轉換子應用
export const hostMap = () => {
    subList.forEach(v => {
        const {url, ...other} = v
        const info = {
            ...other,
            url: url[env]
        }
       subMap[v.name] = info
       subArr.push(info)
    })
   return subArr
}

// 獲取子應用配置資訊
export const getSubMap = name => {
    return subMap[name].show ? subMap[name] : {}
}

子應用注冊預加載和啟動

// setupApp.js
import WujieVue from 'wujie-vue2';
import {hostMap} from './hostMap';

const { setupApp, preloadApp } = WujieVue 

const setUpApp = Vue => {
    Vue.use(WujieVue)
    hostMap().forEach(v => {
        setupApp(v)
        preloadApp(v.name)
    })
}
export default setUpApp;


// main.js
import Vue from 'vue'
import setUpApp from'@/microConfig/setupApp'
setUpApp(Vue)

配置公共函式

全子應用共享的生命周期函式,可用于執行多個子應用間相同的邏輯操作函式共同處理

// lifecycle.js
const lifecycles = {
  beforeLoad: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeLoad 生命周期`),
  beforeMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeMount 生命周期`),
  afterMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterMount 生命周期`),
  beforeUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeUnmount 生命周期`),
  afterUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterUnmount 生命周期`),
  activated: (appWindow) => console.log(`${appWindow.__WUJIE.id} activated 生命周期`),
  deactivated: (appWindow) => console.log(`${appWindow.__WUJIE.id} deactivated 生命周期`),
  loadError: (url, e) => console.log(`${url} 加載失敗`, e),
};

export default lifecycles;


// subCommon.js
// 跳轉到主應用指定頁面
const toJumpMasterApp = (location, query) => {
    
    this.$router.replace(location, query);
    const url = new URL(window.location.href);
    url.search = query
    // 手動的掛載url查詢引數
    window.history.replaceState(null, '', url.href);
}
// 跳轉到子應用的頁面
const toJumpSubApp = (appName, query) => {
   this.$router.push({path: appName}, query)
}
export default {
    toJumpMasterApp,
    toJumpSubApp 
}


// setupApp.js
import lifecycles from './lifecycles';
import subCommon from './subCommon';
const setUpApp = Vue => {
    ....
    hostMap().forEach(v => {
        setupApp({
            ...v,
            ...lifecycles,
            props: subCommon
        })
        preloadApp(v.name)
    })
}

主應用加載子應用頁面

// 子應用頁面加載
// app1.vue
<template>
    <WujieVue
        :key="update"
        
        height="100%"
        :name="name"
        :url="appUrl"
        :sync="subVueApp1Info.sync" 
        :alive="subVueApp1Info.alive" 
        :props="{ data: dataProps ,method:{propsMethod}}"
    ></WujieVue>
</template>

<script>
import wujieVue from "wujie-vue2";
import {getSubMap} from '../../hostMap';
const name = 'subVueApp1'
export default {
    data() {
       return {
          dataProps: [],
          subVueApp1Info: getSubMap(name)
       }
    },
    computed: {
      appUrl() {
        // return getSubMap('subVueApp1').url
        return this.subVueApp1Info.url + this.$route.params.path
      }
    },
     watch: {
        // 如果子應用是保活模式,可以采用通信的方式告知路由變化
        "$route.params.path": {
          handler: function () {
            wujieVue.bus.$emit("vue-router-change", `/${this.$route.params.path}`);
          },
          immediate: true,
        },
      },
    methods: {
        propsMethod() {}
    }
}
</script>

四、子應用配置

無界的插件體系主要是方便用戶在運行時去修改子應用代碼從而避免去改動倉庫代碼

// plugins.js
const plugins = {
  'subVueApp1': [{
    htmlLoader:code => {
      return code;
    },
    cssAfterLoaders: [
      // 在加載html所有樣式之后添加一個外聯樣式
      { src:'https://xxx/xxx.css' },
      // 在加載html所有樣式之后添加一個行內樣式
      { content:'img{height: 300px}' }
    ],
    jsAfterLoaders: [
      { src:'http://xxx/xxx.js' },
      // 插入一個行內腳本本
      { content:`
          window.$wujie.bus.$on('routeChange', path => {
          console.log(path, window, self, global, location)
          })`
      },
      // 執行一個回呼
      {
        callback(appWindow) {
          console.log(appWindow.__WUJIE.id);
        }
      }
    ]
  }],
  'subVueApp2': [{
    htmlLoader: code=> {
      return code;
    }
  }]
};
export default plugins;
// setupApp.js
import plugins from './plugins';
const setUpApp = Vue => {
    ......
    hostMap().forEach(v => {
        setupApp({
            ...v,
            plugins: plugins[element.name]
        })
        ......
    })
}

五、資料傳輸和訊息通信

資料互動方式

1,通過props進行傳

2、通過window進線傳達

3,通過事件bus進行傳達

props

主應用通過data傳參給子應用, 子應用通過methods方法傳參給主應用

// 主應用
<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }"></WujieVue>

// 子應用
const props = window.$wujie?.props; // {data: xxx, methods: xxx}

window

利用子應用運行在主應用的iframe

類似iframe的傳參和呼叫

// 主應用獲取子應用的全域變數資料
window.document.querySelector("iframe[name=子應用id]").contentWindow.xxx;

//子應用獲取主應用的全域變數資料
window.parent.xxx;

eventBus

去中心化的通信方案,方便,類似于組件間的通信

主應用

// 使用 wujie-vue
import WujieVue from"wujie-vue";
const{ bus }= WujieVue;

// 主應用監聽事件
bus.$on("事件名字",function(arg1,arg2, ...){});
// 主應用發送事件
bus.$emit("事件名字", arg1, arg2,...);
// 主應用取消事件監聽
bus.$off("事件名字",function(arg1,arg2, ...){});

子應用

// 子應用監聽事件
window.$wujie?.bus.$on("事件名字",function(arg1,arg2, ...){});
// 子應用發送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2,...);
// 子應用取消事件監聽
window.$wujie?.bus.$off("事件名字",function(arg1,arg2, ...){});

規范主子應用傳遞規則

規則:子應用名+事件名

主應用向子應用傳參

// 主應用傳參
bus.$emit('matser', options) // 主應用向所有子應用傳參
bus.$emit('vite:getOptions', options) // 主應用向指定子應用傳參

//子應用監聽主應用事件
window?.$wujie?.bus.$on("master", (options) => {
  console.log(options)
});
//子應用監聽主應用特定通知子應用事件
window?.$wujie?.bus.$on("vite:getOptions", (options) => {
  console.log(options)
});

六、路由

以 vue 主應用為例,子應用 A 的 name 為 A, 主應用 A 頁面的路徑為/pathA,子應用 B 的 name 為 B,主應用 B 頁面的路徑為/pathB為例

主應用統一props傳入跳轉函式

jump (location) {
  this.$router.push(location);
}

1、主應用history路由

子應用 B 為非保活應用

1、子應用A 只能跳轉到子應用 B 的主應用的默認路由

function handleJump(){
   window.$wujie?.props.jump({ path:"/pathB"});
}

2、子應用A 只能跳轉到子應用B 應用的指定路由(非默認路由)

// 子應用A點擊跳轉處理函式, 子應用B需開啟路由同步
function handleJump(){
    window.$wujie?.props.jump({ path:"/pathB", query:{ B:"/test"}});
}

子應用 B 為保活應用

子應用A 只能跳轉到子應用 B 的主應用的路由

可寫入主應用的插件中,主應用插件根據不同的應用,引入不同方法

// 子應用 A 點擊跳轉處理函式
function handleJump() {
  window.$wujie?.bus.$emit("routeChange", "/test");
}

// 子應用 B 監聽并跳轉
window.$wujie?.bus.$on("routeChange", (path) => this.$router.push({ path }));

2、主應用hash路由

子應用 B 為非保活應用

1、子應用A 只能跳轉到子應用 B 的主應用的默認路由

同子應用B為非保活應用,子應用A跳轉到子應用 B 的主應用的默認路由

2、子應用A 只能跳轉到子應用B 應用的指定路由(非默認路由)

主應用
jump(location,query){ 
    // 跳轉到主應用B頁面
    this.$router.push(location); 
    const url=new URL(window.location.href);
    url.search=query
    // 手動的掛載url查詢引數
    window.history.replaceState(null,"",url.href);
}

// 子應用 B 開啟路由同步能力


// 子應用A
function handleJump() {
  window.$wujie?.props.jump({ path: "/pathB" } , `?B=${window.encodeURIComponent("/test")}`});
}

子應用 B 為保活應用

同子應用B為保活應用,子應用A跳轉到子應用 B 路由

// bus.js
// 在 xxx-sub 路由下子應用將激活路由同步給主應用,主應用跳轉對應路由高亮選單欄
  bus.$on('sub-route-change', (name, path) => {
      const mainName = `${name}-sub`;
      const mainPath = `/${name}-sub${path}`;
      const currentName = router.currentRoute.name;
      const currentPath = router.currentRoute.path;
    if (mainName === currentName && mainPath !== currentPath) {
        router.push({ path: mainPath });
      }
  });

七、部署

前端單頁面的部署,不管怎么自動化,工具怎么變. 都是把打包好的靜態檔案,放到服務器的正確位置下,所以支持專案的獨立部署和混合部署,

作者:京東物流 張燕燕 劉海鼎

內容來源:京東云開發者社區

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/553458.html

標籤:其他

上一篇:影片

下一篇:返回列表

標籤雲
其他(159737) Python(38169) JavaScript(25456) Java(18129) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7211) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5341) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1948) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 前端微服務無界實踐

    隨著專案的發展,前端SPA應用的規模不斷加大、業務代碼耦合、編譯慢,導致日常的維護難度日益增加。同時前端技術的發展迅猛,導致功能擴展吃力,重構成本高,穩定性低。因此前端微服務應運而生。 ......

    uj5u.com 2023-05-26 12:43:27 more
  • 影片

    1. 影片 影片(animation)是CSS3中具有顛覆性的特征之一,可以通過設定都各節點來精確控制一個或一組影片,常用來實作復雜的影片效果。 相比較過度,影片可以實作更多變化,更多控制,連續自動播放等效果。 1.1 影片的基本使用 制作影片分為兩步: (1)先定義影片。 (2)再使用(呼叫)影片 ......

    uj5u.com 2023-05-26 12:43:10 more
  • C#與Node JS互相實作DES加密解密

    具體的加密演算法可以可自行查詢其區別,這里只是拋磚引玉,大部分加密方法基本都能通過改變傳入引數來實作。 C#相關類檔案: System.Security.Cryptography 命名空間 | Microsoft Learn Node JS相關檔案:Crypto | Node.js v16.20.0 ......

    uj5u.com 2023-05-26 12:42:30 more
  • JavaScript 格式化金額

    # JavaScript 格式化金額 ## 一、使用 `toLocaleString()` 要格式化金額,可以使用 JavaScript 的 `toLocaleString()` 方法。該方法可以將數字轉換為本地化的字串表示形式,并可以指定貨幣符號、小數點和千位分隔符等格式。 代碼如下: 1. 美 ......

    uj5u.com 2023-05-26 12:41:58 more
  • 【一步步開發AI運動小程式】八、利用body-calc進行姿態識別

    > 隨著人工智能技術的不斷發展,阿里體育等IT大廠,推出的“樂動力”、“天天跳繩”AI運動APP,讓**云上運動會、線上運動會、健身打卡、AI體育指導**等概念空前火熱。那么,能否將這些在APP成功應用的場景搬上小程式,分享這些概念的紅利呢?本系列文章就帶您一步一步從零開始開發一個AI運動小程式,本 ......

    uj5u.com 2023-05-26 12:29:01 more
  • Nodejs 應用編譯構建提速建議

    前端構建的提速是一項比較復雜且細節的工程, 目前產品上在持續跟蹤構建慢的應用, 努力優化編譯速度, 但前端本身擁有一個比較自由的技識訓境, 沒有統一的構建工具與流程, 另外語言本身的執行效率、單執行緒的構建也不好讓編譯機發揮其最大能力, 所以目前全域的通用優化手段還是會比較局限, 還是依賴專案自身的優... ......

    uj5u.com 2023-05-26 12:17:09 more
  • 搭建自動化 Web 頁面性能檢測系統 —— 設計篇

    >我們是[袋鼠云數堆疊 UED 團隊](http://ued.dtstack.cn/),致力于打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社區積累并傳播經驗價值。。 >本文作者:琉易 [liuxianyu.cn](https://link.juejin.cn/?target=h ......

    uj5u.com 2023-05-26 12:03:06 more
  • CSS中通配輸入文字的小技巧——如何在元素名中包含通配符

    CSS中,*的作用是通配表示“全部”。遺憾的是,并沒有一種通配元素名的方法。 例如,我有好幾個東西class都標記為了my-element-序號,就像這樣: ```html ... ``` 我現在希望讓所有這些class的東西都應用同一個css規則。可惜,css并不支持這么一種寫法: ```css ......

    uj5u.com 2023-05-26 11:57:30 more
  • 記錄--超長溢位頭部省略打點,坑這么大,技巧這么多?

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 在業務中,有這么一種場景,表格下的某一列 ID 值,文本超長了,正常而言會是這樣: 通常,這種情況都需要超長省略溢位打點,那么,就會變成這樣: 但是,這種展示有個缺點,3 個 ID 看上去就完全一致了,因此,PM 希望能夠實作頭部省略打點 ......

    uj5u.com 2023-05-26 11:43:50 more
  • 影片

    1. 影片 影片(animation)是CSS3中具有顛覆性的特征之一,可以通過設定都各節點來精確控制一個或一組影片,常用來實作復雜的影片效果。 相比較過度,影片可以實作更多變化,更多控制,連續自動播放等效果。 1.1 影片的基本使用 制作影片分為兩步: (1)先定義影片。 (2)再使用(呼叫)影片 ......

    uj5u.com 2023-05-26 11:37:34 more