关于前端:NutUI-30-助力企业业务售后门户建设

8次阅读

共计 10498 个字符,预计需要花费 27 分钟才能阅读完成。

我的项目背景

退换 / 售后作为企业洽购业务中不可或缺的一项流程,供企业业务各交易通路应用。为升高企业业务售后的研发老本,对售后相干数据及操作进行对立迭代和保护。咱们打造了一套京东企业业务售后 SaaS 零碎,业务波及范畴蕴含:申请售后,特色业务介绍,售后政策,报表平台。

企业售后门户 SaaS 化的平台建设,可实现以下外围性能:

先来视觉效果上感受一下售后 SaaS 平台~

初窥 SaaS 化平台后,除了满足退换 / 售后,抉择售后类型等根底业务性能外,再来看下此次开发尝鲜的技术栈,以及所落地业务场景的新实现~

Vue 3.0 及其周边生态

2021 新年伊始,尤大就在知乎问答 2021 前端新变动中回复:” 会有很多人摈弃 Webpack 开始用 Vite “。

随着 Vue 3.0 正式公布,其技术栈的周边生态逐步欠缺,如路由插件 Vue-Router,状态治理插件 Vuex,以及紧随生态建设步调的 NutUI 3.0 版本。借此机会,咱们此次开发采纳技术栈为:Vue 3.0 + Vuex4 + Vue-Router4.x + Vite2.2.x + NutUI 3.0 + TS,通过与 Vue3.0 完满联合,开启了新的开发体验~

其中,Vite 应用原生 ESM 文件,基于 ECMAScript 规范原生模块零碎(ES Modules)实现,开发环境中无需打包;Vue 3.0 + Vite 这一开发模式构建速度比传统开发晋升了 30 倍,兼具更轻、更快的 Web 利用开发工具性能;开发模式由 Option api 降级为 Vue3.0 带来的 Compisition api,函数式编程更敌对划分功能模块开发。

全新技术栈的公布,我的项目基础架构的降级,区别于一般的售后模块 SaaS 化售后平台的其余特色业务特色包含:

先来体验一波平台特色性能的实现~

特色业务开发

反对交易平台嵌入应用

京东慧采是为企业级客户打造的研发零投入的专属洽购平台,旗下包含(小程序 /H5/APP)三端不同的落地场景。其中,咱们应用 Taro 技术栈同时开发微信小程序和 H5,实现雷同业务场景下两端落地需要。中心化、SaaS 化的售后模块,将实现一个企业业务挪动端为所有商城类提供售后服务的业务赋能。

慧采挪动端(小程序 /H5/APP)将率先接入企业售后门户:

在京东慧采集体核心页面,点击【退换 / 售后】即可进入第三方接入的售后门户,从操作上来讲一个点击跳转即可实现,那么在逻辑实现上须要做些什么操作?

第三方平台的接入须要买通登陆态解决,在 H5/App 中应用 Cookie 来实现,而小程序的运行环境与浏览器不同,无奈通过同源策略保障平安,在小程序申请接口不会主动带 Cookie,那么怎么同时兼容两者?

H5/APP 登录态的买通

咱们都晓得,H5 跳转第三方页面,关上一个 iframe 标签就能够实现:

<iframe src={webViewUrl}></iframe>

然而要实现不同账户切换下的数据连通,还须要进行登录态的买通解决。

慧采:a.jd.com

售后:b.jd.com

二者的域名不完全相同,Cookie 是不能够跨域名的,失常状况下,同一个一级域名下的两个二级域名也不能交互应用 Cookie

比方:如果想要跳转到售后的 b.jd.com 名下的二级域名都能够应用慧采的 Cookie 信息,须要设置 Cookiedomain 参数为 .jd.com,这样应用 a.jd.comb.jd.com 就能拜访同一个 Cookie 了。

document.cookie = "testCookie=hello;  domain=.jd.com;path=/";

小程序登录态的非凡解决

小程序中第三方页面的跳转依赖 WebView 标签实现:

<WebView src={webViewUrl}></WebView>

那么问题来了,小程序运行环境不是浏览器,在 H5 中登陆态应用的 Cookie 去实现登陆态,Cookie 是通过 申请的 header 带上去的,而在小程序中它的做法是勾销了主动携带的 Cookie,须要手动设置,并且没有了浏览器的同源策略限度。

来看下咱们的解决形式,慧采页面点击【退换 / 售后】前,拿到用户的 pt_key 字段进行加密后,拼接在 url 上,传递到【售后】模块,用作买通登录态的必须信息:

tmpUrl = this.afterSaleUrl + `&pt_key=${pt_key}`;
Utils.openWebView(tmpUrl);

售后模块页面,获取到对应字段后,将 Cookie 种在 jd 域名下,这样的话就能够实现 Cookie 共享了~

// 退出 cookie
const pt_key = this.queryString("pt_key") || "";

if (pt_key) {
  document.cookie = `pt_key=${encodeURIComponent(pt_key)};domain=.jd.com;path=/`;
}

可定制化配置页头

作为一个 SaaS 化平台,应答不同的业务场景总会有不同的客户接入需要,就拿标题栏来讲就有多种需要:

  • 暗藏 / 展现【售后】平台自带标题栏;
  • 应用原场景自带的标题栏,然而读取【售后】标题栏信息;
  • 自定义【售后】标题栏信息


  • 以上 H5 页面中,采纳的是慧采的标题栏,读取【售后】的标题栏信息,那这是如何实现的呢?

对于 iframe 跨域应用 postMessage

同样是获取不同域名下提供的页面服务,简略的 document.title,曾经不能满足咱们的需要了,直觉想到的实现形式就是应用 iframe,此时,咱们能够采纳 postMessage 办法来解决 iframe 间接的交互同样存在的跨域问题。

postMessage 是挂载在 window 下的一个办法,用于不同域名下的两个页面的信息交互,
父子页面通过 postMessage()发送音讯,再通过监听 message 事件接管信息。

来看下实现过程:

售后页面(子页面)给慧采页面(父页面)传递 title 音讯:

window.parent.postMessage({title}, "*");

慧采页面监听 message 事件,子页面发送来的音讯内容在 event.data 属性中:

    _customEvent: () => {};

    customEvent(event) {this.setState({title: event.detail});
        document.title = event.detail;
    }
    componentDidMount() {window.addEventListener('vsptitle', this._customEvent);
    }

响应式款式,一键主题定制~

本次开发的售后 SaaS 平台,针对不同的接入交易平台,为放弃与接入平台的 UI 设计格调保持一致,咱们实现了定制化配置主题性能,依据传入的主题色彩,实时渲染成果。

相较于传统模式只能屡次构建实现,咱们利用了 Vue3.0 的 v-bind:css 新个性实现主题定制实时切换。

咱们在开发后期,也提前将 sass 变量提取了进去:

// 主题色
$primary-color: #f0250f;

// 突变
$gradient-color: linear-gradient(
  135deg,
  rgba(240, 22, 14, 1) 0%,
  rgba(240, 37, 14, 1) 70%,
  rgba(240, 78, 14, 1) 100%
);

// 被选中的浅色背景
$bg-color-selected: #fef4f3;

// 不能点击时背景
$bg-color-disabled: #fcd4cf;

// 背景突变
$bg-gradient-color: $gradient-color;

// 红色背景
$bg-color-white: #fff;

// 边框色彩
$border-color: #ececec;

这样,在重构页面时就能够间接写变量了,也便于前期的款式保护。然而这样,还是不能满足咱们的需要。

能够先理解一下 vue3 sfc),反对在单文件 style 中绑定响应式款式:.head{color: v-bind('theme.color') },间接在 css 下面绑定响应数据。

通过这个个性,咱们能够在初始化时,拿到用户自定义的主题色,而后赋值一个 css 变量,而后去动静批改主题~

const colorState = reactive({
    startColor: '#f0250f',
    endColor: 'linear-gradient(135deg,rgba(240, 22, 14, 1) 0%,rgba(240, 78, 14, 1) 100%'
});

router.isReady().then(async () => {
  ...
  const result = await peelService.getPeelInfo(peelState.peelDto);
    if (result?.state === 0) {
        colorState.startColor = result.value.originalColor;
        colorState.endColor = result.value.terminalColor;
        }
});

<style lang="scss">
.primary-color {color: v-bind(startColor);
}
.gradient-color {background-color: linear-gradient(135deg, v-bind(startColor) 0%, v-bind(endColor) 100%);
}
</style>

这样,咱们在写款式时,就能够间接应用 .primary-color.gradient-color 这样的类名了~

NutUI 3.0 组件赋能业务新形态

京东慧采等多企业洽购平台连续京东 App10.0 的设计格调,这一设计准则和 NutUI 3.0 不约而同。NutUI 3.0 组件库,从视觉设计到技术支持为我的项目落地带来了新的开发体验~

技术看点

  • 引入 Vue3.0 新个性
  • 破坏性变更,全面降级
  • 采纳组合式 API Composition 语法重构,构造清晰,性能模块化
  • 组件 emits 事件独自提取,加强代码可读性
  • 应用 Teleport 新个性重构挂载类组件
  • 构建工具降级为 Vite2.x
  • 全面应用 TypeScipt

以下是本次企业业务售后门户建设,开发用到的 NutUI 3.0 的组件,组件覆盖率达 70%,同时在应用过程中对咱们的组件进行测验和优化:

其中,每个组件笼罩多页面多应用场景,以 PupopToastSwitch 组件为例,看下具体在售后平台页面中的具体应用场景:

接下来,咱们再以这三个组件为例,联合在页面中的应用场景,看下与 Vue 2.x 相比其外部实现原理:

Popup 挂载节点新实现

Popup 组件中有一个十分好用的性能:自行抉择挂载节点,能够将组件挂到任意 DOM 节点上面。

NutUI2.x 中,通过 get-container 属性指定挂载地位:

<!-- 挂载到 #app 节点下 -->
<nut-popup v-model="show" get-container="#app" />

咱们先来看看 NutUI 2.x 版本中挂载节点的外围实现,能够看到,根本的思路是获取到 props 中的 getContainer,而后将以后的组件 append 进去。

portal() {const { getContainer} = this;
  const el = this.$el;

  let container;

  if (getContainer) {container = this.getElement(getContainer);
  } else {return;}

  if (container && container !== el.parentNode) {container.appendChild(el);
  }

  }
}

那么,在 Vue3.0 版本中,咱们是如何实现的呢?
其实非常简单,Vue 3.0 它为咱们提供了一个 Teleport,它容许咱们管制以后在哪个父级下渲染了 DOM,而不用像 2.x 中那么繁琐。

<Teleport :to="teleport">
    ...
</Teleport>

咱们在应用的时候间接通过 props 去传递一个 selector,而后就能够将组件挂载到任何节点下了。是不是特地不便~

<nut-popup
    :style="{padding:'30px 50px'}"
    teleport="#app"
    v-model:visible="state.showTeleport">
    app
</nut-popup>

Toast 函数式调用

Toast 的轻提醒作用,在页面中的应用频率很高:

先看下在 Vue2 的应用形式:

Vue.use(Toast);

// 调用
this.$toast.text("请填写收货人姓名");

在 Vue2.0 中,组件外部实现原理:

Vue.prototype["$toast"] = ToastFunction;

Vue3.0,在页面中应用形式:

app.use(Toast);

import {getCurrentInstance} from 'vue';

setup(){const { proxy} = getCurrentInstance();
  proxy.$toast.text('请填写收货人姓名');
}

先来看下,挂载全局变量看下 Vue 3.0 挂载全局变量的用法,在 main.ts 中引入全局要应用的办法,通过 app.config.globalProperties 增加到全局。

appContext.provides 中注入了一个 Store 实例对象,每个组件都能够应用 this.$store.xxx 拜访 Vuex 中的办法和属性,这时相当于根组件实例和 config 全局配置 globalProperties 中有了 Store 实例对象。

3.0 组件库中,对于 Toast 外部实现原理:

//$toast 增加在全局,在 mian.js 中申明:app.config.globalProperties.$toast = ToastFunction;

Switch 异步控制

在 Vue3.0 中新增异步控制逻辑,其实现原理如下:

利用 Vue3 v-model:model-value 个性进行实现。在应用 v-model 时,代码外部执行 emit('update:value',true); 时将主动同步更新内部传入的变量值。

<nut-switch v-model="checked" />;

import {ref} from "vue";
export default {setup() {const checked = ref(true);
    return {checked};
  },
};

相同咱们只须要采纳 :model-value 进行单向数据下发,进行内部异步控制外部状态值即可:

<nut-switch :model-value="checkedAsync" @change="changeAsync" />

import {ref, getCurrentInstance} from 'vue';
export default {setup() {let { proxy} = getCurrentInstance() as any;
    const checkedAsync = ref(true);
    const changeAsync = (value: boolean, event: Event) => {proxy.$toast.text(`2 秒后异步触发 ${value}`);
      setTimeout(() => {checkedAsync.value = value;}, 2000);
    };

    return {
      checkedAsync,
      changeAsync
    };
  }
};

除了 NutUI 3.0 赋能的业务场景外,我的项目根底构建层还包含以下几点:

以数据管理和路由配置 / 办法封装两方面,来看下联合 Vue3.0 的应用,在企业业务中实现形式:

Vuex4 如何优雅地进行状态治理

针对售后列表中某一具体的商品来讲,后续【抉择售后类型】,【申请售后】等操作的数据项简直雷同,为防止频繁读取接口和频繁应用组件传餐的形式来同步数据,缩小数据的治理和保护工作,咱们采纳将数据值定义在 Vuex 中供其余页面应用。

此次开发咱们采纳最新版本的 Vuex4,那么基于 Vue2.x 格调的 Vuex 的应用形式是否还实用于 Vue3?

Vue2.x 中,为实现代码复用采取的 getter/action 形式,以及为不不便独自编写治理模块而设计的 module。Vue3 中曾经补全了 Vue2 的短板,所以 Vuex 应用形式相应产生了变动。

useStore 反对 ts 应用配置 injection key

Vue3 中 Vuex 的 useStore 具备残缺的 statemodules 类型揣测,对 ts 反对也失去了增强。可是 Vuex4 对 ts 的反对却没有任何扭转,应用 useStore 的类型依然为 any,在此官网提供了解决方案:

InjectionKey 注入类型

  1. 定义类型 InjectionKey。
  2. InjectionKey 在将商店装置到 Vue 应用程序时提供类型。
  3. 将类型传递 InjectionKey 给 useStore 办法。
// store.ts
import {InjectionKey} from "vue";
import {createStore, createLogger, Store} from "vuex";
// 手动申明 state 类型
export interface State {
  orderItem: any;
  customerExpectResultList: any;
  orderSkuApplyItem: any;
  //...
}
// 定义注入类型
export const key: InjectionKey<Store<State>> = Symbol();

接下来,装置到 Vue 应用程序时传递定义的注入类型:

// main.ts
import {createApp} from "vue";
import {store, key} from "@/store";

const app = createApp(App);

//pass the injection key
app.use(store, key);
app.mount("#app");

最初,将密匙传递给 useStore 办法以检索类型化的存储。

import {useStore} from 'vuex';
import {key} from '@/store';

const store = useStore(key);

onMounted(async () => {
            state.provideName = await getProvideName(store.state.orderItem.jdOrderId,);
});

Vue 3.0 如何配置 / 治理页面路由

页面跳转实现依附路由来实现,新版路由配置和之前十分类似,只有些许不同。新版本路由的 API 全副采纳函数式引入的形式,配合 ts 的类型提醒,让咱们无需文档也可能实现配置。

路由配置和 2.x 的形式保持一致:

export const routes: RouteRecordRaw[] = [
  {
    path: paths.saleindex,
    name: "saleindex",
    component: SaleIndex,
    meta: {
      title: "退换 / 售后",
      keepAlive: true,
      backurl: "",
      style: {},},
  },

  //...

  {path: "/:path(.*)+",
    redirect: () => paths.saleindex,},
];

此处由 new VueRouter 的形式批改为 createRoute 形式,其余无变动。路由模式的配置采纳 API 调用的形式,不再是之前的字符串,此处采纳的 hash 路由。

import {createRouter, createWebHashHistory} from "vue-router";
import {routes} from "./routes";

const router = createRouter({history: createWebHashHistory(),
  routes,
});

export default router;

当初,一个 Vue3 的根底路由就配置实现了,接着在 main.ts 这个入口文件中插件的形式通过 Vue 引入就能够了~

import {createApp} from "vue";
import App from "./App.vue";
import router from "@/router/router";

const app = createApp(App);
app.use(router);
app.mount("#app");

router 的封装

页面引入:

<div class="button">
    <nut-button plain type="primary" @click="goDetail()"> 查问详情 </nut-button>
</div>

export default defineComponent({
    name: 'submitsuccess',
    setup: () => {const { push} = useRouterHelper();

        const goDetail = () => {push(paths.saleindex, { type: 1});
        };

        onMounted(async () => {});

        return {goDetail,};
    }
});

办法封装:

export const useRouterHelper = () => {const router = useRouter();

    const push = <T extends paths, K extends keyof ParamsMap>(path: T, params?: ParamsMap[K]) => {
        router.push({
            path,
            query: params
        });
    };

    return {push};
};

最初,聊聊 TS 的 Decorator ~

咱们在进行页面开发的时候,往往有这么一个需要,须要在适合的中央增加 loading 成果:

预计之前大多数开发者做法是这样的:

public async request(url: string, method: string, params: any): Promise<ResponseData | null> {
            ...
      //loading start
      Toast.loading();
            const res = await axios(options as AxiosRequestConfig);
      //loading end
      Toast.hide();
            return this.checkStatus(res);
        } catch (error) {return error;}
    }

咱们在封装的对立申请函数中,增加页面 loading 成果,这样也可能满足需要。然而 … 就有产品、测试、业务揪着这个问题不放,我哪里哪里不须要 loading。😫

作为搬砖 🧱 的咱们,不得不另找出路去解决这个烦人的问题,怎么办???⚡️ 想到了暴力解决法,也是比拟麻烦和工作量大的方法,咱们在申请函数中增加一个标识参数来管制是否增加 loading,当然,这样能够解决下面奇葩的需要,有没有优雅一点的解决方案呢?

对于 Decorator ~

首先,它是一种和类(class)相干的语法,理解 JavaPython 的同学大略晓得它次要是用来干嘛的。在咱们的 ECMAScript 中也提到了这种语法,用来做同样的事件。根本的语法:@ decorator

有了它,下面的问题能够这样解决,减少 loading 装璜器,在须要 loading 的接口上方减少 @loading

export class SaleIndexService {
    ...
    /**
     * @description:售后申请
     * @param:OrderPageReqDTO 实体类
     * **/
    @loading()
    findOrderPage(params: OrderPageReqDTO) {return this.http.request('/api/afs/findOrderPage.do', 'post', params);
    }

    /**
     * @description:申请记录
     * @param:ServicePageReqDTO 实体类
     *
     * ***/
    @loading()
    findServicePage(params: ServicePageReqDTO) {return this.http.request('/api/afs/find/service/page', 'post', params);
    }

    ...
}

下面咱们定义了一个类,它能够在页面中初始化,而后调用相应的 API,那么,咱们能够定义这样一个 loading 装璜器,在须要的申请接口办法下面增加,这样,就能够做到自定义加载 loading 的需要了,是不是看起来比方才的实现高大上~😂

//loading decorator

export const loading = () => {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const method = descriptor.value;

    descriptor.value = async function (...args: any[]): Promise<any> {
      let res: any;
      _Toast.loading("加载中");
      try {res = await method.apply(this, args);
      } catch (error) {throw error;} finally {_Toast.hide();
      }
      return res;
    };

    return descriptor;
  };
};

结语

售后 SaaS 平台这一套性能全面、用户体验良好的前端零碎,不仅实现了对售后数据及操作进行对立治理和保护,实现售后零碎能力输入的同时,再赋能到其它畛域。随着 Vue 3.0 等技术栈利用的不断深入,其全新的个性会被逐个全面利用,NutUI 3.0 组件库随之也会失去更好的迭代和更新,为京东企业业务驱动我的项目开发削减了新的能源。此外,NutUI 也继续推出了小程序版本的适配,欢送关注~本文旨在抽时间梳理一下本我的项目开发中的一些思考点,尽可能多共享一些经验总结,同时也能零碎回顾和坚固本身。

正文完
 0