Project background

As an indispensable process in enterprise procurement business, return/after-sales service is used by all transaction channels of enterprise business. In order to reduce the r&d cost of after-sale business, the unified iteration and maintenance of after-sale related data and operations were carried out. We have created a set of AFTER-SALES SaaS system for JINGdong enterprise business, whose business scope includes: after-sales application, special business introduction, after-sales policy and report platform.

The SaaS platform construction of enterprise after-sales portal can realize the following core functions:First to feel the visual effects of the after-sales SaaS platform ~

After the initial glimpse of SaaS platform, in addition to meet the basic business functions such as return/after-sales service, select after-sales service type, we will look at the development of the technology stack, and the implementation of the new business scenarios ~

Vue 3.0 and its surrounding ecology

“A lot of people will abandon Webpack and start using Vite,” said UVU in a q&A about the new changes in front of 2021.

With the official release of Vue 3.0, the ecosystem around the technology stack has been gradually improved, such as vuE-Router for routing, Vuex for state management, and NutUI 3.0 for eco-building. We take this opportunity to use the technology stack: Vue3.0 + Vuex4 + VUE-Router4. x + Vite2.2.x + NutUI 3.0 + TS, through the perfect combination with Vue3.0, opened a new development experience ~

Vite uses native ESM files, which are implemented based on ECMAScript standard ES Modules, without packaging in the development environment. Vue 3.0 + Vite is 30 times faster than traditional development, with lighter and faster Web application development tools. The development mode is upgraded from Option API to Compisition API introduced by Vue3.0, and functional programming is more friendly to divide functional module development.

The release of the new technology stack, the upgrade of the project infrastructure, and other features of the SaaS after-sales platform, which is different from the ordinary after-sales module, include:

First to experience the realization of a wave of platform features ~

Featured business development

Support the embedded use of trading platform

Jingdong Huicai is an exclusive procurement platform for enterprise customers with zero investment in research and development, which includes three different landing scenarios (small program /H5/APP). Among them, we use Taro technology stack to develop wechat applet and H5 at the same time to realize the landing requirements of both ends in the same business scenario. The centralized and SaaS after-sales module will realize an enterprise business mobile terminal to provide after-sale services for all shopping malls.

Huicai mobile terminal (small program /H5/APP) will take the lead in accessing enterprise after-sales portal:In jingdong Huicai personal center page, click “return/after-sales” to enter the third party access to the after-sales portal, from the operation of a click to jump can be completed, so in the logic of the implementation of what to do?

Third-party platform access needs to get through the login state processing, using cookies in H5/App to achieve, and the running environment of the small program is different from the browser, can not ensure security through the same origin policy, in the small program request interface will not automatically with cookies, so how to simultaneously compatible with both?

H5/APP login mode is cleared

As we all know, H5 jumps to a third party page, opening an iframe tag can be done:

<iframe src={webViewUrl}></iframe>
Copy the code

However, in order to realize the data connectivity under the switching of different accounts, it is necessary to open the login state.

Hui mining: a.jd.com

After sale: b.jd.com

The domain names of the two domains are different. Therefore, cookies cannot be cross-domain names. Normally, two second-level domain names under the same first-level domain name cannot exchange cookies.

Such as: If you want to jump to the secondary domain name of b.jd.com after sale, you can use the Cookie information of Huicai. You need to set the domain parameter of Cookie to.jd.com. This allows both a.jd.com and b.jd.com to access the same Cookie.

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

Special processing of small program login state

The jump of third-party pages in the applet relies on the WebView tag to achieve:

<WebView src={webViewUrl}></WebView>
Copy the code

In H5, the login mode uses the Cookie to realize the login mode. The Cookie is carried by the header requested. In the small program, it cancels the automatic Cookie, which needs to be set manually. And there are no browser same-origin policy restrictions.

Take a look at our solution, huicai page click “return/after sale”, get the user’s PT_key field after encryption, Mosaic in the URL, transfer to the [after sale] module, used as the necessary information through the login state:

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

After obtaining the corresponding field on the after-sales module page, the Cookie is planted under the JD domain name, so that Cookie sharing can be realized

/ / add a cookie
const pt_key = this.queryString("pt_key") | |"";

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

The configuration header can be customized

As a SaaS platform, there will always be different customer access requirements for different business scenarios. Take the title bar as an example, there are various requirements:

  • Hide/show the title bar of the platform;
  • Use the title bar of the original scene, but read [after-sales] title bar information;
  • Customize [after-sales] title bar information
  • .

Above H5 page, the use of huicai title bar, read [after sale] title bar information, that is how to achieve it?

About iframe using postMessage across domains

To obtain the same page services provided under different domain names, simple document.title can no longer meet our needs. The intuitive way to achieve this is to use iframe. We can use the postMessage approach to solve the same cross-domain problem that exists with direct iframe interactions.

PostMessage is a method mounted under a window that is used to communicate messages between two pages under different domains. The parent page sends messages through postMessage () and receives messages by listening for message events.

Let’s look at the implementation:

After sales page (child page) to Huicai page (parent page) to transfer the title message:

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

A collection page listens for a message event, and the content of the message sent by the child page is in the event. Data property:

	_customEvent: () = > {};

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

Responsive style, one button theme customization ~

The after-sales SaaS platform developed in this time is targeted at different access trading platforms. In order to keep consistent with the UI design style of the access platform, we have realized the customized configuration theme function and real-time rendering effect according to the incoming theme color.Compared with the traditional model, which can only be built multiple times, we use Vue3.0v-bind:cssNew feature enables real-time switching of theme customization.

At the early stage of development, we also extracted the sass variable in advance:

/ / theme color
$primary-color: #f0250f;

/ / the gradient
$gradient-color: linear-gradient(
  135deg,
  rgba(240.22.14.1) 0%,
  rgba(240.37.14.1) 70%,
  rgba(240.78.14.1) 100%);// The selected light background
$bg-color-selected: #fef4f3;

// Can't click on the background
$bg-color-disabled: #fcd4cf;

// Background gradient
$bg-gradient-color: $gradient-color;

// White background
$bg-color-white: #fff;

// Border color
$border-color: #ececec;
Copy the code

In this way, you can write variables directly when refactoring the page, which is also convenient for later style maintenance. But that still doesn’t meet our needs.

Head {color: V-bind (‘theme.color’)} to bind the response data directly to the CSS.

With this feature, we can take the custom theme color at initialization, assign a CSS variable, and dynamically modify the theme ~

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>
Copy the code

In this way, we can use the.primary-color,.gradient-color class names directly when writing styles

NutUI 3.0 component enabling business in new form

Jingdong Huicai and other multi-enterprise procurement platforms continue the design style of JINGdong App10.0, and this design principle coincides with NutUI 3.0. NutUI 3.0 component library, from visual design to technical support, brings new development experience to project landing

Technology to watch

  • New Vue3.0 features are introduced

  • Disruptive change, full upgrade

  • Using combined API Composition syntax reconstruction, clear structure, modular function

  • Component emits events are extracted separately to enhance code readability

  • Refactoring mount class components using Teleport’s new feature

  • Upgrade the build tool to Vite2.x

  • Use TypeScipt across the board

The following are the NutUI 3.0 components used in the development of the enterprise business after sale portal. The component coverage reaches 70%. Meanwhile, our components are checked and optimized in the process of use:

Each component covers multiple pages and multiple usage scenarios. Take Pupop, Toast and Switch components as examples to see the specific usage scenarios in the pages of after-sales platforms:

Next, let’s take these three components as an example and take a look at their internal implementation compared to Vue 2.x, combined with their usage scenarios in the page:

Popup new implementation of mounting nodes

The Popup component has a very handy feature: you can mount the component under any DOM node of your choice.

In nutui2. x, use the get-container attribute to specify the mount location:

<! Mount to #app --><nut-popup v-model="show" get-container="#app" />
Copy the code

Let’s take a look at the core implementation of mounting nodes in NutUI 2.x. The basic idea is to get getContainer in props and append the current component.

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); }}}Copy the code

So how do we do that in Vue3.0? It’s actually quite simple, Vue 3.0 gives us a Teleport that allows us to control which parent renders the DOM at the moment without the complexity of 2.x.

<Teleport :to="teleport">... </Teleport>Copy the code

When we use it, we pass a selector directly through props, and then we can mount the component to any node. Is it particularly convenient

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

Toast is called as a function

ToastIs used frequently in the page:

Let’s take a look at how Vue2 is used:

Vue.use(Toast);

/ / call
this.$toast.text("Please fill in the name of consignee");
Copy the code

In Vue2.0, the internal implementation principle of components is as follows:

Vue.prototype["$toast"] = ToastFunction;
Copy the code

Vue3.0, how to use in the page:

app.use(Toast);

import { getCurrentInstance } from 'vue';

setup(){
  const { proxy } = getCurrentInstance();
  proxy.$toast.text('Please fill in the name of consignee');
}
Copy the code

Look at first, mount the global variables and the Vue 3.0 mount the use of the global variables, in the main, ts introduced in global method is used, through the app. Config. GlobalProperties added to the global.

Each component can use this.$store. XXX to access methods and properties in Vuex. This is equivalent to having a Store instance object in the root component instance and config global configuration globalProperties.

In the 3.0 component library, the internal implementation principle of Toast:

//$toast is added globally, declared in mian.js:
app.config.globalProperties.$toast = ToastFunction;
Copy the code

Switch Asynchronous control

Asynchronous control logic is added in Vue3.0. The implementation principle is as follows:

Vue3 V-model and: Model-value features are used to implement. When using v-Model, the code internally emits emit(‘ Update :value’,true); Is automatically synchronized to update externally passed variable values.

<nut-switch v-model="checked"/ >;import { ref } from "vue";
export default {
  setup() {
    const checked = ref(true);
    return{ checked }; }};Copy the code

Instead, we only need to use :model-value for one-way data delivery and external asynchronous control of internal state 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('Asynchronously triggered after 2 seconds${value}`);
      setTimeout(() = > {
        checkedAsync.value = value;
      }, 2000);
    };

    return{ checkedAsync, changeAsync }; }};Copy the code

In addition to the NutUI 3.0 enabled business scenarios, the project infrastructure layer includes the following:

From the two aspects of data management and route configuration/method encapsulation, let’s see how Vue3.0 can be implemented in enterprise business.

How can VEX4 elegantly manage state

After-sales list for a specific commodity, subsequent choice after type, 】 【 application after-sales 】 operations such as almost the same data item, to avoid frequent read interface and frequent use of components and meal to synchronize data, reduce data management and maintenance work, we use the data values defined for the use of other pages in the Vuex.

In this development, we adopted the latest version of Vuex4, so will the use of vue2.x style Vuex still apply to Vue3?

In Vue2. X, getter/ Action for code reuse and Modules designed to make it inconvenient to write administrative modules separately. Vue3 has completed the shortcomings of Vue2, so the way of using Vuex has changed accordingly.

UseStore supports TS to configure the Injection key

Vuex’s useStore in Vue3 has full state and modules type speculation, and support for TS has also been enhanced. Vuex4’s support for TS has not changed at all. The useStore type is still any.

InjectionKey Injection type

  1. Define the type InjectionKey.
  2. InjectionKey provides the type when the store is installed into the Vue application.
  3. Pass the type InjectionKey to the useStore method.
// store.ts
import { InjectionKey } from "vue";
import { createStore, createLogger, Store } from "vuex";
Copy the code
// Manually declare the state type
export interface State {
  orderItem: any;
  customerExpectResultList: any;
  orderSkuApplyItem: any;
  / /...
}
Copy the code
// Define the injection type
export const key: InjectionKey<Store<State>> = Symbol(a);Copy the code

Next, pass the defined injection type when installing into the Vue application:

// 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");
Copy the code

Finally, the key is passed to the useStore method to retrieve typed storage.

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

const store = useStore(key);

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

How to Configure and manage page routing on Vue 3.0

The page hop implementation relies on routing, and the new routing configuration is very similar to the previous one, with only minor differences. The new version of the routing API is all introduced functionally, along with ts type hints, allowing us to complete configuration without documentation.

The route configuration is the same as that of 2.x:

export const routes: RouteRecordRaw[] = [
  {
    path: paths.saleindex,
    name: "saleindex".component: SaleIndex,
    meta: {
      title: "Return/After Sale".keepAlive: true.backurl: "".style: {},}},/ /...

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

The new VueRouter mode is changed to createRoute mode. Other parameters remain unchanged. The route mode is configured using API calls instead of strings. Hash routes are used here.

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

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

export default router;
Copy the code

Now a Vue3 base route is configured and imported via Vue as a plug-in in the main.ts entry file

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

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

The router to encapsulate

Page introduction:

<div class="button">
	<nut-button plain type="primary" @click="goDetail()">For details</nut-button>
</div>

export default defineComponent({
	name: 'submitsuccess'.setup: () = > {

		const { push } = useRouterHelper();

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

		onMounted(async () => {

		});

		return{ goDetail, }; }});Copy the code

Method encapsulation:

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 }; };Copy the code

Finally, let’s talk about TS decorators

When developing a page, there is a need to add loading effects in the right place:

In the past, most developers would have done something like this:

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) {
			returnerror; }}Copy the code

We added a page loading effect to the wrapped unified request function, which also satisfied the requirements. But… There are products, tests, and businesses that are obsessed with this. I don’t need to load anywhere. 😫

As we move brick 🧱, have to find another way to solve this annoying problem, how to do?? ⚡️ came up with a violent solution, which is also a troublesome and heavy workload solution. We added an identification parameter to the request function to control whether to add loading. Of course, this can solve the above weird requirements.

About the Decorator ~

First of all, it’s a class syntax, so those of you who know Java and Python probably know what it’s mostly for. This syntax is also mentioned in our ECMAScript to do the same thing. Basic syntax: @decorator

Loading decorator: add @loading above the interface where loading is required:

export class SaleIndexService {.../ * * *@description: After-sale application *@param: OrderPageReqDTO entity class ** */
	@loading()
	findOrderPage(params: OrderPageReqDTO) {
		return this.http.request('/api/afs/findOrderPage.do'.'post', params);
	}

	/ * * *@description: Application record *@param: ServicePageReqDTO entity class ** ***/
	@loading()
	findServicePage(params: ServicePageReqDTO) {
		return this.http.request('/api/afs/find/service/page'.'post', params); }... }Copy the code

The loading decorator can be added to the required interface method, so that the loading requirement can be customized. This is not a more expensive implementation. 😂

//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("Loading");
      try {
        res = await method.apply(this, args);
      } catch (error) {
        throw error;
      } finally {
        _Toast.hide();
      }
      return res;
    };

    return descriptor;
  };
};
Copy the code

conclusion

After-sales SaaS platform this set of comprehensive functions, good user experience of the front-end system, not only to achieve unified management and maintenance of after-sales data and operations, complete the after-sales system capacity output at the same time, and then to other fields. With the deepening of Vue 3.0 and other technology stack application, its new features will be fully applied one by one, NutUI 3.0 component library will be better iteration and update, for jingdong enterprise business driven project development added new power. In addition, NutUI has continued to release miniprogram versions of the adaptation. Welcome to this article, I will take a moment to review some of the thinking points in the development of the project, share as many lessons as possible, and systematically review and consolidate myself.