【微前端】singleSpa&importHTMLEntry(流程图)源码解析

简介: https://www.hsbang.com/sitemap.xml

single-spa v5.9.3

通过轻量级路由劫持和状态机设计,实现微前端的动态加载与隔离,主要实现

  • 路由管理:hashchange、popstate、history.pushState、history.replaceState进行劫持,路由变化时,触发 reroute()
  • 子应用状态管理:不同执行逻辑转化不同的状态,比如
  • 加载流程:toLoadPromise→toBootstrapPromise→toMountPromise
  • 卸载流程:toUnmountPromise→toUnloadPromise
  • 子应用生命周期触发:
  • app.bootstrap():初始化时仅执行一次
  • app.mount():应用激活时触发
  • app.unmount():应用从激活变为非激活状态时触发
  • app.unload():最终卸载时触发一次

single-spa 采用 JS Entry 的方式接入微前端

我们需要在基座中注册子应用,比如下面代码中,我们注册了对应的映射路径 path 以及对应的加载的方法

registerApplication({
  name: "app1",
  app: loadApp(url),
  activeWhen: activeWhen("/app1"),
  customProps: {},
});

整体流程图

1.png

1. registerApplication()

在基座初始化时,会调用 registerApplication() 进行子应用的注册

从下面的源码我可以知道,主要执行:

  • 格式化用户传递的子应用配置参数:sanitizeArguments()
  • 将子应用加入到 apps 中
  • 如果是浏览器,则触发
  • ensureJQuerySupport():增加 JQuery的支持
  • reroute():统一处理路由的方法
function registerApplication(
  appNameOrConfig,
  appOrLoadApp,
  activeWhen,
  customProps
) {
  var registration = sanitizeArguments(
    appNameOrConfig,
    appOrLoadApp,
    activeWhen,
    customProps
  );
  apps.push(
    assign(
      {
        loadErrorTime: null,
        status: NOT_LOADED,
        parcels: {},
        devtools: {
          overlays: {
            options: {},
            selectors: [],
          },
        },
      },
      registration
    )
  );
  if (isInBrowser) {
    ensureJQuerySupport();
    reroute();
  }
}

reroute()

  • 状态计算:通过 getAppChanges() 根据当前的 URL 筛选出需要 加载/卸载 的应用,主要分为 4 种类型
  • 根据是否已经触发 start(),从而决定要触发
  • loadApps(): 加载应用资源,没有其他逻辑
  • performAppChanges():卸载非 active 状态的应用(调用 umount 生命周期) + 加载并挂载 active 子应用
function reroute() {
  if (appChangeUnderway) {
    return new Promise(function (resolve, reject) {
      peopleWaitingOnAppChange.push({
        resolve: resolve,
        reject: reject,
        eventArguments: eventArguments,
      });
    });
  }
  var { appsToUnload, appsToUnmount, appsToLoad, appsToMount } =
    getAppChanges();
  if (isStarted()) {
    appChangeUnderway = true;
    appsThatChanged = appsToUnload.concat(
      appsToLoad,
      appsToUnmount,
      appsToMount
    );
    return performAppChanges();
  } else {
    appsThatChanged = appsToLoad;
    return loadApps();
  }
}

1.1 getAppChanges()

根据目前 app.status 的状态进行不同数组数据的组装

  • appsToLoad
  • appsToUnload
  • appsToMount
  • appsToUnmount
apps.forEach(function (app) {
  var appShouldBeActive =
    app.status !== SKIP_BECAUSE_BROKEN && shouldBeActive(app);
  switch (app.status) {
    case LOAD_ERROR:
      if (appShouldBeActive && currentTime - app.loadErrorTime >= 200) {
        appsToLoad.push(app);
      }
      break;
    case NOT_LOADED:
    case LOADING_SOURCE_CODE:
      if (appShouldBeActive) {
        appsToLoad.push(app);
      }
      break;
    case NOT_BOOTSTRAPPED:
    case NOT_MOUNTED:
      if (!appShouldBeActive && getAppUnloadInfo(toName(app))) {
        appsToUnload.push(app);
      } else if (appShouldBeActive) {
        appsToMount.push(app);
      }
      break;
    case MOUNTED:
      if (!appShouldBeActive) {
        appsToUnmount.push(app);
      }
      break;
    // all other statuses are ignored
  }
});

1.2 loadApps()

在 loadApps() 中,就是遍历 appsToLoad 数组 => toLoadPromise(app),本质就是触发 app.loadApp()进行子应用的加载

function loadApps() {
  return Promise.resolve().then(function () {
    var loadPromises = appsToLoad.map(toLoadPromise);
    return (
      Promise.all(loadPromises)
        .then(callAllEventListeners)
        // there are no mounted apps, before start() is called, so we always return []
        .then(function () {
          return [];
        })
        .catch(function (err) {
          callAllEventListeners();
          throw err;
        })
    );
  });
}
1.2.1 toLoadPromise()

触发 app.loadApp()进行子应用的加载

需要子应用提供一个 loadApp()并且返回 Promise

状态改为 NOT_BOOTSTRAPPED

function toLoadPromise(app) {
  return Promise.resolve().then(function () {
    if (app.loadPromise) {
      return app.loadPromise;
    }
    if (app.status !== NOT_LOADED && app.status !== LOAD_ERROR) {
      return app;
    }
    app.status = LOADING_SOURCE_CODE;
    var appOpts, isUserErr;
    return (app.loadPromise = Promise.resolve()
      .then(function () {
        var loadPromise = app.loadApp(getProps(app));
        return loadPromise.then(function (val) {
          app.loadErrorTime = null;
          appOpts = val;
          app.status = NOT_BOOTSTRAPPED;
          app.bootstrap = flattenFnArray(appOpts, "bootstrap");
          app.mount = flattenFnArray(appOpts, "mount");
          app.unmount = flattenFnArray(appOpts, "unmount");
          app.unload = flattenFnArray(appOpts, "unload");
          app.timeouts = ensureValidAppTimeouts(appOpts.timeouts);
          delete app.loadPromise;
          return app;
        });
      })
      .catch(function (err) {
        //...
      }));
  });
}

2. 监听路由变化

single-spa 源码中有自动执行的一系列代码:

  • 监听 hashchange 和 popstate 变化,触发urlReroute()->reroute()
  • 劫持 window.addEventListener 和 window.removeEventListener,将外部应用通过注册的 ["hashchange", "popstate"] 的监听方法 放入到 capturedEventListeners 中,在下面的 unmountAllPromise.then() 之后才会调用 capturedEventListeners 存储的方法执行
  • 重写 history.pushState() 和 history.replaceState() 方法,在原来的基础上增加 window.dispatchEvent(createPopStateEvent(window.history.state, methodName)) ,从而触发第一步的 popstate 监听,从而触发 urlReroute()->reroute() 进行子应用路由的状态同步

总结:

  • 路由变化触发微前端子应用加载
  • pushState 和 replaceState 改变路由触发微前端子应用加载
  • 阻止外部的hashchange、popstate对应的监听方法直接执行,而是等待微前端执行后才触发这些方法
var routingEventsListeningTo = ["hashchange", "popstate"];
if (isInBrowser) {
  window.addEventListener("hashchange", urlReroute);
  window.addEventListener("popstate", urlReroute);

  var originalAddEventListener = window.addEventListener;
  var originalRemoveEventListener = window.removeEventListener;
  window.addEventListener = function (eventName, fn) {
    if (typeof fn === "function") {
      if (
        routingEventsListeningTo.indexOf(eventName) >= 0 &&
        !find(capturedEventListeners[eventName], function (listener) {
          return listener === fn;
        })
      ) {
        capturedEventListeners[eventName].push(fn);
        return;
      }
    }
    return originalAddEventListener.apply(this, arguments);
  };
  window.removeEventListener = function (eventName, listenerFn) {
    //...
  };
  window.history.pushState = patchedUpdateState(
    window.history.pushState,
    "pushState"
  );
  window.history.replaceState = patchedUpdateState(
    window.history.replaceState,
    "replaceState"
  );
  if (window.singleSpaNavigate) {
    //...
  } else {
    window.singleSpaNavigate = navigateToUrl;
  }
}

function urlReroute() {
  reroute([], arguments);
}

function callAllEventListeners() {
  pendingPromises.forEach(function (pendingPromise) {
    callCapturedEventListeners(pendingPromise.eventArguments);
  });
  callCapturedEventListeners(eventArguments);
}

3. start()启动开始状态

当基座主动触发 single-spa 的 start() 方法时

function start(opts) {
  started = true;
  if (opts && opts.urlRerouteOnly) {
    setUrlRerouteOnly(opts.urlRerouteOnly);
  }
  if (isInBrowser) {
    reroute();
  }
}

此时已经在监听路由变化,然后进行 active 子应用的挂载 performAppChanges()

function reroute() {
  if (appChangeUnderway) {
    return new Promise(function (resolve, reject) {
      peopleWaitingOnAppChange.push({
        resolve: resolve,
        reject: reject,
        eventArguments: eventArguments,
      });
    });
  }
  var { appsToUnload, appsToUnmount, appsToLoad, appsToMount } =
    getAppChanges();
  if (isStarted()) {
    appChangeUnderway = true;
    appsThatChanged = appsToUnload.concat(
      appsToLoad,
      appsToUnmount,
      appsToMount
    );
    return performAppChanges();
  } else {
    appsThatChanged = appsToLoad;
    return loadApps();
  }
}

3.1 getAppChanges()

根据目前 app.status 的状态进行不同数组数据的组装

  • appsToLoad
  • appsToUnload
  • appsToMount
  • appsToUnmount
apps.forEach(function (app) {
  var appShouldBeActive =
    app.status !== SKIP_BECAUSE_BROKEN && shouldBeActive(app);
  switch (app.status) {
    case LOAD_ERROR:
      if (appShouldBeActive && currentTime - app.loadErrorTime >= 200) {
        appsToLoad.push(app);
      }
      break;
    case NOT_LOADED:
    case LOADING_SOURCE_CODE:
      if (appShouldBeActive) {
        appsToLoad.push(app);
      }
      break;
    case NOT_BOOTSTRAPPED:
    case NOT_MOUNTED:
      if (!appShouldBeActive && getAppUnloadInfo(toName(app))) {
        appsToUnload.push(app);
      } else if (appShouldBeActive) {
        appsToMount.push(app);
      }
      break;
    case MOUNTED:
      if (!appShouldBeActive) {
        appsToUnmount.push(app);
      }
      break;
    // all other statuses are ignored
  }
});

3.2 performAppChanges()

而当 start() 方法触发后,started 设置为 true, 标志着应用从 初始化注册应用(加载应用)的模式进入到 运行阶段(监听路由变化)


此时触发 reroute(),则进入 performAppChanges()

urlRerouteOnly控制路由触发规则:

  • urlRerouteOnly=true:用户点击或者使用 API 才会触发 reroute()
  • urlRerouteOnly=false:任何 history.pushState() 的调用都会触发 reroute()
function start(opts) {
  started = true;
  if (opts && opts.urlRerouteOnly) {
    setUrlRerouteOnly(opts.urlRerouteOnly);
  }
  if (isInBrowser) {
    reroute();
  }
}

在 performAppChanges() 中,先组装出需要卸载的子应用

var unloadPromises = appsToUnload.map(toUnloadPromise);
var unmountUnloadPromises = appsToUnmount
  .map(toUnmountPromise)
  .map(function (unmountPromise) {
    return unmountPromise.then(toUnloadPromise);
  });
var allUnmountPromises = unmountUnloadPromises.concat(unloadPromises);
var unmountAllPromise = Promise.all(allUnmountPromises);

再组装出需要加载的应用

/* We load and bootstrap apps while other apps are unmounting, but we
 * wait to mount the app until all apps are finishing unmounting
 */
var loadThenMountPromises = appsToLoad.map(function (app) {
  return toLoadPromise(app).then(function (app) {
    return tryToBootstrapAndMount(app, unmountAllPromise);
  });
});
/* These are the apps that are already bootstrapped and just need
 * to be mounted. They each wait for all unmounting apps to finish up
 * before they mount.
 */
var mountPromises = appsToMount
  .filter(function (appToMount) {
    return appsToLoad.indexOf(appToMount) < 0;
  })
  .map(function (appToMount) {
    return tryToBootstrapAndMount(appToMount, unmountAllPromise);
  });

先触发 unmountAllPromise ,然后再触发 loadThenMountPromises.concat(mountPromises),最终全部完成后触发finishUpAndReturn

return unmountAllPromise
  .catch(function (err) {
    callAllEventListeners();
    throw err;
  })
  .then(function () {
    /* Now that the apps that needed to be unmounted are unmounted, their DOM navigation
     * events (like hashchange or popstate) should have been cleaned up. So it's safe
     * to let the remaining captured event listeners to handle about the DOM event.
     */
    callAllEventListeners();
    return Promise.all(loadThenMountPromises.concat(mountPromises))
      .catch(function (err) {
        pendingPromises.forEach(function (promise) {
          return promise.reject(err);
        });
        throw err;
      })
      .then(finishUpAndReturn);
  });

在上面的方法中,我们看到了很多封装的方法,比如toLoadPromise()、tryToBootstrapAndMount()、toUnloadPromise()、finishUpAndReturn(),接下来我们将展开分析

3.2.1 tryToBootstrapAndMount()

当用户目前的路由是 /app1,导航到 /app2时:

  • 调用 app.activeWhen() 进行子应用状态的检测(需要子应用提供实现方法),shouldBeActive(app2) 返回 true
  • 触发 toBootstrapPromise(app2) 更改状态为 BOOTSTRAPPING,并且触发子应用提供的 app2.bootstrap() 生命周期方法 => 更改状态为 NOT_MOUNTED
  • 触发传入的unmountAllPromise,进行 /app1 卸载,然后再触发 toMountPromise(app2) 执行子应用提供的 app2.mount() 生命周期方法,然后更改状态为 MOUNTED

如果卸载完成 /app1 后,我们再次检测 shouldBeActive(app2) 的时候发现路由改变,不是 /app2,那么 app2 停止挂载,直接返回 app2,状态仍然保留在 toBootstrapPromise(app2) 时的 NOT_MOUNTED

function tryToBootstrapAndMount(app, unmountAllPromise) {
  if (shouldBeActive(app)) {
    return toBootstrapPromise(app).then(function (app) {
      return unmountAllPromise.then(function () {
        return shouldBeActive(app) ? toMountPromise(app) : app;
      });
    });
  } else {
    return unmountAllPromise.then(function () {
      return app;
    });
  }
}
function shouldBeActive(app) {
  return app.activeWhen(window.location);
}
function toBootstrapPromise(appOrParcel, hardFail) {
  return Promise.resolve().then(function () {
    if (appOrParcel.status !== NOT_BOOTSTRAPPED) {
      return appOrParcel;
    }
    appOrParcel.status = BOOTSTRAPPING;
    if (!appOrParcel.bootstrap) {
      // Default implementation of bootstrap
      return Promise.resolve().then(successfulBootstrap);
    }
    return reasonableTime(appOrParcel, "bootstrap")
      .then(successfulBootstrap)
      .catch(function (err) {
        //...
      });
  });
  function successfulBootstrap() {
    appOrParcel.status = NOT_MOUNTED;
    return appOrParcel;
  }
}
function toMountPromise(appOrParcel, hardFail) {
  return Promise.resolve().then(function () {
    return reasonableTime(appOrParcel, "mount")
      .then(function () {
        appOrParcel.status = MOUNTED;
        //...
        return appOrParcel;
      })
      .catch(function (err) {
        //...
      });
  });
}
3.2.2 toUnloadPromise()

逻辑也非常简单,就是触发子应用提供的 app2.unload() 生命周期方法,将状态改为 UNLOADING => 将状态改为 NOT_LOADED

var appsToUnload = {};
function toUnloadPromise(app) {
  return Promise.resolve().then(function () {
    var unloadInfo = appsToUnload[toName(app)];

    if (app.status === NOT_LOADED) {
      finishUnloadingApp(app, unloadInfo);
      return app;
    }
    if (app.status === UNLOADING) {
      return unloadInfo.promise.then(function () {
        return app;
      });
    }
    if (app.status !== NOT_MOUNTED && app.status !== LOAD_ERROR) {
      return app;
    }
    var unloadPromise =
      app.status === LOAD_ERROR
        ? Promise.resolve()
        : reasonableTime(app, "unload");
    app.status = UNLOADING;
    return unloadPromise
      .then(function () {
        finishUnloadingApp(app, unloadInfo);
        return app;
      })
      .catch(function (err) {
        errorUnloadingApp(app, unloadInfo, err);
        return app;
      });
  });
}
function finishUnloadingApp(app, unloadInfo) {
  delete appsToUnload[toName(app)];
  delete app.bootstrap;
  delete app.mount;
  delete app.unmount;
  delete app.unload;
  app.status = NOT_LOADED;
  unloadInfo.resolve();
}
3.3.3 finishUpAndReturn()
  • 返回已经挂载应用的列表
  • 处理等待中的 pendingPromises
  • 触发全局事件通知
  • 重置全局状态 appChangeUnderway = false
  • 检测是否有未处理的后续请求,如果有,则重新触发 reroute() 处理
function finishUpAndReturn() {
  var returnValue = getMountedApps();
  pendingPromises.forEach(function (promise) {
    return promise.resolve(returnValue);
  });
  var appChangeEventName =
    appsThatChanged.length === 0
      ? "single-spa:no-app-change"
      : "single-spa:app-change";
  window.dispatchEvent(
    new customEvent(appChangeEventName, getCustomEventDetail())
  );
  window.dispatchEvent(
    new customEvent("single-spa:routing-event", getCustomEventDetail())
  );

  appChangeUnderway = false;
  if (peopleWaitingOnAppChange.length > 0) {
    var nextPendingPromises = peopleWaitingOnAppChange;
    peopleWaitingOnAppChange = [];
    reroute(nextPendingPromises);
  }
  return returnValue;
}

import-html-entry v1.17.0

假设我们要转化的 index.html 为:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Hello Micro Frontend</h1>
    <script src="app.js" entry></script>
    <script src="async.js" async></script>
    <script>console.log('Inline script');</script>
</body>
</html>

style.css 的具体内容为:

body { background-color: lightblue; }

app.js 内容为:

// 子应用导出的生命周期钩子
export function bootstrap() {
  console.log("Sub app bootstrap");
}
export function mount() {
  console.log("Sub app mounted");
}
bootstrap();

async.js 内容为:

console.log("Async script loaded");

我们在外部使用这个库一般直接使用 importEntry() 获取子应用的数据

在我们这个示例中,会传入一个 entry = "index.html",因此会直接走 importHTML()

function importEntry(entry, opts = {}) {
  const {
    fetch = defaultFetch,
    getTemplate = defaultGetTemplate,
    postProcessTemplate,
  } = opts;
  const getPublicPath =
    opts.getPublicPath || opts.getDomain || defaultGetPublicPath;

  // html entry
  if (typeof entry === "string") {
    return importHTML(entry, {
      fetch,
      getPublicPath,
      getTemplate,
      postProcessTemplate,
    });
  }

  // config entry
  if (Array.isArray(entry.scripts) || Array.isArray(entry.styles)) {
    //...
  } else {
    throw new SyntaxError("entry scripts or styles should be array!");
  }
}

从下面代码可以知道,主要分为几个步骤:

  • 获取 HTML 内容:通过fetch直接请求对应的 https 得到对应的 HTML 字符串(也就是我们上面示例的 index.html 内容)
  • 解析 HTML:调用 processTpl() 解析 HTML 得到
  • scripts = ["/app.js ", ""]
  • entry = "/app.js "
  • styles = ["/style.css "]
  • 将 CSS 样式进行内联:在 getEmbedHTML() 下载 style.css 的内容,替换 template 模板中 为 的內联样式
    function getEmbedHTML(template, styles, opts = {}) {
      const { fetch = defaultFetch } = opts;
      let embedHTML = template;
    
      return getExternalStyleSheets(styles, fetch).then((styleSheets) => {
        embedHTML = styleSheets.reduce((html, styleSheet) => {
          const styleSrc = styleSheet.src;
          const styleSheetContent = styleSheet.value;
          html = html.replace(
            genLinkReplaceSymbol(styleSrc),
            isInlineCode(styleSrc)
              ? `${styleSrc}`
              : `<style>/* ${styleSrc} */${styleSheetContent}</style>`
          );
          return html;
        }, embedHTML);
        return embedHTML;
      });
    }
    

    3. 返回值对象解析

    3.1 template

    替换 template 模板中 为 內联样式替换到 template 中,然后返回一个对象数据,包括

    • template:替换了所有 styles 的模板数据
    • assetPublicPath:微应用的路径
    • getExternalScripts():提供外部调用可以下载 scripts = ["/app.js ", ""] 的方法
    • getExternalStyleSheets():提供外部调用可以下载 styles = ["/style.css "] 的方法
    • execScripts():执行 getExternalScripts() 下载 scripts,然后调用 geval() 生成沙箱代码并执行,确保 JS 在代理的上下文中运行,避免全局污染
    function importHTML(url, opts = {}) {
      //...
      return (
        embedHTMLCache[url] ||
        (embedHTMLCache[url] = fetch(url)
          .then((response) => readResAsString(response, autoDecodeResponse))
          .then((html) => {
            const assetPublicPath = getPublicPath(url);
            const { template, scripts, entry, styles } = processTpl(
              getTemplate(html),
              assetPublicPath,
              postProcessTemplate
            );
    
            return getEmbedHTML(template, styles, { fetch }).then((embedHTML) => ({
              template: embedHTML,
              assetPublicPath,
              getExternalScripts: () => getExternalScripts(scripts, fetch),
              getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
              execScripts: (proxy, strictGlobal, opts = {}) => {
                if (!scripts.length) {
                  return Promise.resolve();
                }
                return execScripts(entry, scripts, proxy, {
                  fetch,
                  strictGlobal,
                  ...opts,
                });
              },
            }));
          }))
      );
    }
    
目录
相关文章
|
1月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
166 1
|
2月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
272 70
|
2月前
|
存储 前端开发 JavaScript
调用DeepSeek API增强版纯前端实现方案,支持文件上传和内容解析功能
本方案基于DeepSeek API增强版,提供纯前端实现的文件上传与内容解析功能。通过HTML和JavaScript,用户可选择文件并调用API完成上传及解析操作。方案支持多种文件格式(如PDF、TXT、DOCX),具备简化架构、提高响应速度和增强安全性等优势。示例代码展示了文件上传、内容解析及结果展示的完整流程,适合快速构建高效Web应用。开发者可根据需求扩展功能,满足多样化场景要求。
|
3月前
|
前端开发 算法 NoSQL
前端uin后端php社交软件源码,快速构建属于你的交友平台
这是一款功能全面的社交软件解决方案,覆盖多种场景需求。支持即时通讯(一对一聊天、群聊、文件传输、语音/视频通话)、内容动态(发布、点赞、评论)以及红包模块(接入支付宝、微信等第三方支付)。系统采用前后端分离架构,前端基于 UniApp,后端使用 PHP 框架(如 Laravel/Symfony),配合 MySQL/Redis 和自建 Socket 服务实现高效实时通信。提供用户认证(JWT 集成)、智能匹配算法等功能,助力快速上线,显著节约开发成本。
83 1
前端uin后端php社交软件源码,快速构建属于你的交友平台
|
2月前
|
监控 前端开发 小程序
陪练,代练,护航,代打小程序源码/前端UNIAPP-VUE2.0开发 后端Thinkphp6管理/具备家政服务的综合型平台
这款APP通过技术创新,将代练、家政、娱乐社交等场景融合,打造“全能型生活服务生态圈”。以代练为切入点,提供模块化代码支持快速搭建平台,结合智能匹配与技能审核机制,拓展家政服务和商业管理功能。技术架构具备高安全性和扩展性,支持多业务复用,如押金冻结、录屏监控等功能跨领域应用。商业模式多元,包括交易抽成、增值服务及广告联名,同时设计跨领域积分体系提升用户粘性,实现生态共生与B端赋能。
188 12
|
6月前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
514 5
|
7月前
|
机器学习/深度学习 编解码 前端开发
探索无界:前端开发中的响应式设计深度解析####
【10月更文挑战第29天】 在当今数字化时代,用户体验的优化已成为网站与应用成功的关键。本文旨在深入探讨响应式设计的核心理念、技术实现及最佳实践,揭示其如何颠覆传统布局限制,实现跨设备无缝对接,从而提升用户满意度和访问量。通过剖析响应式设计的精髓,我们将一同见证其在现代Web开发中的重要地位与未来趋势。 ####
114 7
|
7月前
|
编解码 前端开发 UED
探索无界:前端开发中的响应式设计深度解析与实践####
【10月更文挑战第29天】 本文深入探讨了响应式设计的核心理念,即通过灵活的布局、媒体查询及弹性图片等技术手段,使网站能够在不同设备上提供一致且优质的用户体验。不同于传统摘要概述,本文将以一次具体项目实践为引,逐步剖析响应式设计的关键技术点,分享实战经验与避坑指南,旨在为前端开发者提供一套实用的响应式设计方法论。 ####
188 4
|
7月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
162 1
|
7月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
107 1