preface

Almost all management system we have written, I first Vue project is to be the backstage management system, from the beginning to think that it is hard to make it feel very simple, after up to now many excellent open source projects can directly bring them here to use, I’m thinking of doing an ebook sharing system, in the process of doing as much as possible to complete each section with modular thought, It’s a test of your own growth.

Project initialization

This project is based on the middle and background management system developed by Vue-Element-Admin, and the whole management system is built by them as the project framework

git clone https://github.com/PanJiaChen/vue-element-admin.git xxx
cd xxx
npm i
npm run dev
Copy the code

Once the login has started successfully, you can continue to simplify the code by removing some unnecessary files.

  • Delete source code from SRC /views and keep it:
    • Dashboard homepage
    • Error-page: Exception page
    • Login: login
    • Redirect: indicates the redirection
  • Modify the SRC /router/index. If you delete any component, delete the dynamic load route configuration
  • Delete the SRC /router/modules folder
  • Delete the SRC /vendor folder
  • Delete unnecessary files and folders

Note โš  : Componetnts folder do not delete, and so on after the project online to delete the components that are not used

Project configuration

If you don’t want to configure it, you can directly down my repository. I have committed the historical version at every step

  • Source address ๐ŸŒŸ : github.com/it-beige/bo…
  • Version history โญ : a1c3e95146d9de2091a7402408b2bfff4c0aee3d

SRC /settings.js for global configuration:

  • Title: site title. After entering a page, the format is:
Page Title - Site titleCopy the code
  • ShowSettings: whether to display the right hover configuration button
  • TagsView: Whether to display the page tag function bar
  • FixedHeader: Whether to fix the header layout
  • SidebarLogo: Indicates whether to display the LOGO on the menu bar
  • ErrorLog: The default environment for displaying error logs

The source code to debug

If you need to debug the source code, you need to modify vue.config.js(environment Vue-cli4)

Common configuration items in the development environment:

  • Eval: Packaging is fast, but it maps transcoded code instead of the original code (there is no source mapping from the loader), so it does not display line numbers correctly
  • Eval-source-map: This is slow at first, but the rebuild is fast and can generate real files. Line numbers are mapped correctly
  • Source-map: The ability to see the entire SRC source code directly in the browser is great for formatting, but should not be used in production

vue.config.js

productionSourceMap: process.env.NODE_ENV === 'development', (modified)configureWebpack: {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : undefined, (modification)name: name,
    resolve: {
      alias: {
        The '@': resolve('src')}}},Copy the code

Modify the above two on ok

Rendering ๐Ÿ’—

Does this mode say more cool?

The project structure

File the effect

Build [package] mock [mock] node_modules [third-party package] public [root file] SRC [source: Editorconfig [editor configuration file].env.devlopment [development environment configuration file].env.production [Production environment configuration file].env.test [Test environment configuration file] editorConfig [editor configuration file].env.devlopment [development environment configuration file].env.test [Test environment configuration file] .eslintignore [eslint ignore verify configuration file].eslintrc.js [eslint verify configuration file].gitignore [git commit ignore configuration file] babel.config.js [Babel configuration file].eslintignore [eslint ignore verify configuration file].eslintrc.js [eslint verify configuration file].gitignore [git commit ignore configuration file].babel config. Jsoncifg. json [js file personalization profile: Json [third-party package file information] postcss.config.js [file for CSS extensions: Add CSS compatibility prefix, etc.] reamde.md [project document] vue.config.js [vue-CLI configuration file]Copy the code

Note โš  : Not all the above configuration files need to be configured, but they are common configuration files in these projects. Some unnecessary configuration files can be deleted by themselves, and it is recommended to keep them

Project structure [source]

SRC | - all the API interface API source directory | - static resource assets, Images, ICONS, styles, etc. | | - components utility components - directives custom command | - filters filter, global tools | | - the ICONS icon components - layout: Layout components | - the router routing, | - store vuex unified management, unified management | - styles: custom style | - utils: General utility methods directory | | - views view | - role role module name | | -- - | -- role - the list. The vue role list page | | -- - | -- role - the add. Vue role new page | | -- - | -- - Role - update. Vue role updates page | | -- - | - components role module common component folder | -- permission. Js login authentication and routing hop | -- Settings. Js: global configurationCopy the code

Common project libraries

Common UI Frameworks

  • Elements-ui A UI framework developed by Ele. me to work with the framework
  • Ant Design is an enterprise UI framework for Ant Financial
  • Vant has a lightweight, reliable mobile Vue component library developed by our technical team
  • Cube-UI Didi Team’s mobile component library
  • NutUI JD.com team: a set of jd.com style mobile terminal component library

Load configuration plug-ins on demand (Load on demand applies to all of the above UI frameworks)

JS library

  • Lodash: very famous JS library
  • Underscore: The JS utility library that provides a complete set of functional programming utility features
  • Ramda: Common library for functional programming
  • Velocity: JavaScript animation suite
  • Validator.js: Form validation
  • Wangeditor.js: rich text editor

Note โš  : There are also many JS libraries, but in fact, many of the functions provided by the JS libraries above are integrated with the UI library, and we don’t need to learn every one of them, we just need to play gitHub, when the project needs to look at the documentation you can easily get started, so we don’t need to learn everything. When your project needs it, you can learn about it. It’s not too difficult to learn about the libraries and frameworks.

Encapsulate your own Storage

When combing through the whole project framework, I found that the author did not encapsulate a passed Stroage. Here, we encapsulate a universal Stroage to carry out a unified management of the stored data required by the current project, and put all the data under the ‘book’ module

The whole idea is to wrap all the sotarge related to this project in one module so that it doesn’t get messy.

It looks messy like this.

  • Get the entire storage directly
  • Gets/sets the key of the specified module
  • Clear specified/all modules
/ * * *@info Packaging Storage * /

const GLOBAL_MODULE_NAME = 'book';
let cacheStorage = {}


class Storage {
  constructor(type) {
    this.type = type;
  }

  // Obtain the storage of the entire module
  getStorage() {
    return JSON.parse(window[this.type].getItem(GLOBAL_MODULE_NAME)) || {}
  }

  / / set
  setItem(key, value, moduleName) {
    if (moduleName) {
      let val = this.getItem(moduleName)
      val[key] = value;
      this.setItem(moduleName, val)
    } else {
      let val = this.getStorage()
      val[key] = value
      window[this.type].setItem(GLOBAL_MODULE_NAME, JSON.stringify(val))
    }
  }

  / / to get
  getItem(key, moduleName) {
    if (JSON.stringify(cacheStorage) === '{}') {
      cacheStorage = this.getStorage()
    }

    if (moduleName) {
      let val = cacheStorage[moduleName]
      if (val) return val[key]
    }
    return cacheStorage[key]
  }

  / / delete
  removeItem(key, moduleName) {
    let val = this.getStorage();
    if (moduleName) {
      delete val[moduleName][key]
    } else {
      delete val[key]
    }
    window[this.type].setItem(GLOBAL_MODULE_NAME, JSON.stringify(val))
  }
}

export default Storage
Copy the code

Use it next in main.js

Vue.prototype.sessionStorage = new Storage('sessionStorage')
Vue.prototype.localStorage = new Storage('localStorage')
Copy the code

Test this out on the Login page

let userInfo = {
	name: "ks"
};
this.localStorage.setItem('userInfo', userInfo)
this.localStorage.setItem('age'.18.'userInfo')
Copy the code

Login module modification

Rendering ๐Ÿ’—

  • Source address ๐ŸŒŸ : github.com/it-beige/bo…
  • History version โญ : 16 f87edf6da62088a4fefd585035d1e195dd2153

โ

Online humble, if you think this article is helpful to you welcome everyone to click ๐Ÿ‘ป

โž

Common scenarios Encapsulate components twice

In everyday project development we are less likely to write code for repetitive scenarios. Take logins for example: logins must have a user name and password. There might be some other form items, so we might encapsulate a common form component, and next time we can just introduce it and write down the configuration items to show the effect directly.

Simple example โšก

If we have a form that requires nine form items, this is a good example of the benefits of component encapsulation.

bad

<el-input type="text" v-model="Value1" maxLength ="130" minlength="120" :placeholder=" username ":clearable="false" :disabled="false" :required="true" @focus="handleMyFocus" @blur="handleMyBlur" @input="handleModelInput" @clear='handleMyClear' > </el-input> <el-input type="text" v-model="Value2" maxlength="130" minlength="120" :placeholder=" password ":clearable="true" :disabled="true" :required="true" @focus=" handleMyBlur" @blur="handleMyBlur" @input="handleModelInput" @clear='handleMyClear' > </el-input> .... x7Copy the code

Alright

template

<template> <div class="my-input"> <el-input :type="type" v-model="curValue" :maxlength="maxlength" :minlength="minlength" :placeholder="fillPlaceHolder" :clearable="clearable" :disabled="disabled" :autofocus="autofocus"  :required="required" @focus="handleMyFocus" @blur="handleMyBlur" @input="handleModelInput" @clear='handleMyClear' > </el-input> </div> </template>Copy the code

script

export default {
  name: 'MyInput'.props: {
    type: {
      validator(val) {
        return(['text'.'number'.'tel'.'password'.'email'.'url'].indexOf(val) ! = = -1); }},value: {
      required: true.type: [Number.String],},... Parameter Verification.... }data() {
    return {
      curValue: this.value,
      focus: false.fillPlaceHolder: ' '}; },methods: {
    handleMyFocus(event) {
      this.focus = true;
      this.$emit('focus', event)
      if (this.placeholder && this.placeholder ! = =' ') {
        this.fillPlaceHolder = this.placeholder; }},... Like above, throw events from outside; </script>Copy the code

use

<div v-for="(item, index) in inputList" :key="index"> <label>.... </label> <my-input type="email" :value="item.val" /> </div> <script> export default { data() { return { inputList: [ {type: 'text', val: ''}, {type: 'eamil', val: '', ....} ] } } } </script>Copy the code

Analytical dynamic – form โšก

Start with the configuration items

<dynamic-form labelWidth="0px" :formConfig="formConfig" v-model="loginForm" ref="loginForm" :showBtn="false" > <slot Name ="slot-box"> Good common components must be very scalable, slots are a good solution <slot> <dynamic-form>Copy the code

dynamic-form

First, take a look at the internal structure of the component

Template section

<el-form :model="value" :ref="refName" :label-width="labelWidth" ... > <el-row :gutter="space" :span="24" class="form-item"> <el-col v-show="formItemHidden(item.hidden)" :md="item.span || itemSpanDefault" :xs="xsSpan" :offset="item.offset || 0" > <dynamic-form-item ...... > </dynamic-form-item> </el-col> <el-row :gutter="space"> <el-col :span="24"> <div class="slot-box"> <slot></slot> </div> </el-col> </el-row> <el-row :gutter="space"> <el-col :span="24"> <el-form-item v-if="showBtn && ! Disabled "class="form-bottom-box"> <el-button type="primary" @click="submitForm"> Submit </el-button> <el-button @ click = "cancelForm" > cancel < / el - button > < / el - form - item > < / el - col > <! --> <el-form-item v-else class="form-bottom box"> <slot name="formBottom"><slot> </el-form-item> </el-row> </el-form>Copy the code

Above is the general structure, looking at the whole, roughly understand:

  • El-row/EL-COL: As an outer layer of each form item, a simple item response is made
  • Dynamic-form-item: Indicates that the form internally introduces its own encapsulated form item (see more later)
  • Slot-box: slots are provided for components
  • The final form button configuration item simply makes the configuration of whether there is a button or not, which is not ideal (we changed it a little bit, if there is no button, we usually check the status of the form, maybe there is a prompt under the form).

Script part

(Props, data, filters, components…) (Watch, computed, hook function…) .

This is to give you an idea of the data that the component provides. It’s good to have a general idea of the data that the component has: props, data, and mixins.

import { deepClone } from "@/utils"; // Used to deep clone objects

Copy the code

props/data

props: {
  refName: { // You can customize the ref(reference) name for the el-form used internally by the component
    type: String.default: "dynamicForm"
  },
  formConfig: { // Form configuration items (important)
    type: Object.required: true
  },
  disabled: Boolean.// Whether to disable
  value: { // Data (important)
    type: Object.required: true
  },
  labelWidth: { // The width of the form field tag
    type: String.default: "80px"
  },
  showBtn: {
    type: Boolean.// Whether to display the button
    default: true
  },
  space: {
    type: Number./ / form
    default: 20
  },
  columnMinWidth: {
    type: String}},data() {
 	return {
   	xsSpan: 24.itemSpanDefault: 24.options: [].// The extracted configuration item
    cascadeMap: new Map(),
    hasInitCascadeProps: [].allDisabled: false}; }Copy the code

The most important configuration items are formConfig and value, both of which are object types and must be passed

provide/watch

provide() {
   return {
     formThis: this
   };
 },
 watch: {
   formConfig: {
     handler() {
       this.initConfig();
     },
     deep: true.// Deep listening, each member under the formConfig object will respond
     immediate: true // Start the hanlder}},Copy the code

Provide this may be unfamiliar to some of you, but let me be careful.

It is a way for components to communicate: it allows an ancestor component to inject a dependency into all of its descendants, regardless of how deep the component hierarchy is, for as long as the upstream and downstream relationships are established

Provide allows you to inject data into your descendants’ components so that they can share the data with you.

Take โšก for example

If B is A child of A and C is A child of B, then C is A descendant of A

// A.vue
export default {
  provide: {
    formThis: this // Insert the Vue instance directly
  }
  data() {
  	return {
    	obj: {name: 'north song'}}}}Copy the code

Inject provides two ways to obtain the dependency injected by the ancestor. Generally, use the second way

// B.vue
export default {
  inject: ['formThis'],
  mounted () {
    console.log(this.formThis);  // You can access the instance object of the ancestor component, and the data in the parent component}}Copy the code
// C.vue
export default {
  inject: {
    formThis: {default: {}} // The ancestor component instance object, if not, is null by default to prevent program error
  },, 
  mounted () {
    console.log(this.formThis.obj.name);  // =>}}Copy the code

methods

The overview provides several methods

  • UploadSuccess upload related
  • ItemClass defines style dependence
  • InitConfig Initializes the configuration item
  • The handleInput action form item is triggered
  • ChangeSelect changeSelect triggers
  • HandlerCascade the name of handlerCascade doesn’t tell you anything
  • initCascadeOptions
  • SetDefaultValue Sets the default value
  • SubmitForm Submission form
  • ResetForm Resets the form
  • CancelForm cancelForm
  • HandleSubmit Operation button
  • FormItemHidden Hides a form item

Ok, so now that we have a rough idea of some of the functionality that the component provides internally, let’s look at how to implement it.

Source code Step 1

First find the source code in the “live” things, must remember: look at the source must not be seen from beginning to end, the source of all the points involved all look at it, this way is not recommended, it is a waste of time, most of the time we just carefully read we want to learn a functional point on the line

For example, some edge points of this form component:

  • DeepClone Clone method
  • The formUpload component in the dynamic-form-item component

These are not the points that we want to know for the moment, but the internal implementation of dynamic-form-item is something that I’ll talk about in more detail

The dynamic-form component has no hook function, so the only thing that’s alive is the formConfig component that watch listens on. So let’s start with that.

watch
watch: {
  formConfig: {
    handler() {
      this.initConfig(); // This method is called
    },
    deep: true.immediate: true}},Copy the code
Configuration items

A separate title is not meaning, is to analyze the source code, the configuration item data to analyze the source code, but also easy to jump

FormConfig: {formItemList: [{key: "username", type: "input", icon: "user", placeholder:" [{required: true, trigger: "blur", message: "please enter the correct user name"}, {required: true, trigger: "blur", validator: validatePassword } ] } ]Copy the code
InitConfig method
initConfig() {
  if (this.formConfig.allDisabled) { // No configuration
    this.allDisabled = true;
  }
  this.formConfig.formItemList.forEach((item, index) = > {
    if (item.hasOwnProperty("cascade")) { // No configuration
      const ob = {
        index: index,
        url: item.url,
        key: item.key,
        multiple: item.multiple
      };
      this.cascadeMap.set(item.cascade, ob); }});this.options = deepClone(this.formConfig.formItemList); 
  this.setDefaultValue();
},

Copy the code

From the above method, you can configure the global disable form items, form items can be configured with cascade, which we did not use, will not look at for the moment.

setDefaultValue
setDefaultValue() {
  constformData = { ... this.value };// value -> v-model Specifies the data bound to the v-model
  // Set the default value
  this.options.forEach(item= > { // options is the configuration item -> formConfig
    const { key, value } = item;
    if (formData[key] === undefined || formData[key] === null) {
      formData[key] = value; // No key is set, and the value is treated as a key
    }
    if (formData[key]) { // Initialize the key
      this.initCascadeOptions(formData[key], key); // Pass the value first, then the key}});this.$emit("input", { ...formData }); 
},

Copy the code

From the above method we know that the component internally throws an input event, we can get all the data === loginForm

initCascadeOptions
initCascadeOptions(val, key) {
  CascadeMap says that there is no set member when we do not configure the 'cascade' attribute
  if (this.cascadeMap.has(key)) { 
    const obj = this.cascadeMap.get(key);
    if (this.hasInitCascadeProps.includes(obj.key)) return;
    if (val) {
      const object = deepClone(this.formConfig.formItemList[obj.index]);
      Object.keys(object.params).forEach(key= > {
        if (!object.params[key]) {
          object.params[key] = val;
        }
      });
      this.$set(this.options, obj.index, object);
      this.hasInitCascadeProps.push(obj.key); }}},Copy the code

Method execution is now complete, and you can begin to build the structure and render the page.

<el-form .... > <el-row ... > <el-col v-for="item in options" :key="item.key" v-show="formItemHidden(item.hidden)" :md="item.span || itemSpanDefault" :xs="xsSpan" :offset="item.offset || 0" > <dynamic-form-item class="item" :allDisabled="allDisabled" False :ref="item.key" input :item="item" Is an object type, binding object, Value ="value[item.key]" loginForm['username'] :disabled="disabled" style="{'min-width': @input="handleInput($event, $event) "handleInput($event, $event) Item.key)" @ChangesElect ="changeSelect" @uploadSuccess="uploadSuccess" </dynamic-form-item> </el-col> </el-row> </el-form>Copy the code

Render to EL-Col and trigger the formItemHidden method. The form item can be configured with the hidden property to control whether it is displayed or hidden

Before we look at the dynamic-form-item component, let’s look at a few highlights of the component

  • Ref =”item.key” : a configuration reference can be made to each form, and the name is the key name
  • :item=”item” binds each form item, and components can be internally qualified based on configuration items

Analyzing the source code we know the components:

  • You can configure the disabling of individual forms
  • You can set labelWidth for a separate form
  • You can listen to: changeSelect, uploadSuccess, handleInput

Analytical dynamic – form – item โšก

After paving the way for this component, we now have the core of the source code

Template section

<el-forn-item :class="{ 'is-look': disabled}" :label="item.label" :prop="item.key" :rules="item.rules" > <span v-if="item.icon" class="svg-container"> <svg-icon :icon-class="item.icon" /> </span> <el-input v-if=" ['input', 'text', 'password', 'email', 'textarea', 'number'].includes( item.type ) " v-bind="$attrs" :type="item.subtype || item.type" :class="{'all-disabled': allDisabled}" v-on="$listeners" resize="none" :autosize="disabled ? true : item.autoSize || { minRows: 4, maxRows: 6 }" :placeholder="! isDisabled ? item.placeholder : ''" :disabled="isDisabled" @focus="handleFocusEvent(item.key)" > </el-input> <el-radio-group v-else-if="item.type === 'radio'"></el-radio-group> <el-checkbox-grou v-else-if="item.type === 'checkbox'"></el-checkbox-group> <el-select v-else-if="item.type === 'select'"></el-select > <el-tree-select v-else-if="item.type === 'select'"></tree-select> <el-switch v-else-if="item.type === 'switch'"></el-switch> <el-time-picker v-else-if="item.type === 'time'"></el-time-picker> <el-date-picker v-else-if="item.type === 'date'"></el-date-picker> <form-upload v-else-if="item.type === 'file'"></form-upload> <dynamic-data-box v-else-if="item.type === 'databox'"></dynamic-data-box> <template v-else-if="item.type === 'slot'"> <slot :name="item.key" :data="$attrs.value"></slot> </template> </el-forn-item>Copy the code

Overall, the component supports a number of form types, but there is definitely extra processing done internally to reference other secondary encapsulated components, and it can be said that this component covers most business scenarios where forms are used.

  • Single/multiple choice
  • Drop down/tree drop down
  • The switch control
  • Time/Date
  • File upload
  • The dynamic form
  • Finally, a slot for the form item is provided

We’re not using a lot of types here, so I’m just going to parse the input form and look briefly at the source code above

  • If the form is disabled then there will be a disabled style
  • Allows a form to be configured with a separate validation rule, which is not included in the el-form rules
  • Allow configuration icon
  • The Input form supports all types

Highlights โญ

<el-input v-bind="$attrs" // class="{'all-disabled': AllDisabled ="$listeners"; // Disabled ="isDisabled"; // Disabled ="isDisabled" @focus="handleFocusEvent(item.key)" listening for native events > </el-input>Copy the code

$attrs/$listeners

Another communication mode is used here: $attrs/$Listeners, which is also used to communicate across components. Here I’ll explain it to you in detail

  • $attrs: contains feature bindings (except class and style) whose parent scope is not recognized (and retrieved) by prop. When a component does not declare any prop, all parent bindings (except class and style) are included, and internal components can be passed through v-bind=”$attrs”. Usually used with the interitAttrs option.
  • $listeners: contains a V-on event listener in the parent scope (without.native modifier). It can pass in internal components via V-on =”$Listeners”

example

In this scenario, component A refers to component B, component B refers to component C, and all events thrown by component C can be monitored by component B through $listeners, and all attributes in component A (parent component) can be obtained by component B (child component) through $attrs. But if you’ve already used props to receive data from the parent, the $attrs object will not have this attribute.

A component

<B :foo="foo" :coo="coo" :eee="eee" @uphot ="upHot" @blurhandle ="blurHanlde" > </B> </div> </template> <script> import B from './B' export default {name:'A', data() {return {foo: 'north song ', COO :' front self learning station ', }}, Components :{B}, methods:{upHot(){console.log(" If you think this article is good, please like it!" )}, blurHanlde() {console.log(' Thanks for your support! '); } } } </script>Copy the code

B component

< the template > < div class = "B" > < div class = "title" > I am B < / div > < p > foo: {{foo}} < / p > < p > attrs: {{$attrs}} < / p > < C v - bind = "$attrs" v-on="$listeners"></C> </div> </template> <script> import C from './C'; export default { name:'B', inheritAttrs:false, components: {C}, props:["foo"], data() { return { BAttr: }}, methods: {blurHandle() {console.log('B component listened '); } }, mounted() { console.log(this.$attrs); } } </script>Copy the code

C components

< span style =" box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 13px! Important; word-break: inherit! Important;" <input @blur="myBlur"/> </div> </template> <script> export default { name: "childDomChild", props: ["coo"], methods: { TopUp() { this.$emit("upHot"); Console. log(" Congratulations on your hit! ); }, myBlur() { this.$emit("blurHandle"); Log (this.$attrs) // {eee: "wechat itbeige: reply group, join this full stack project exchange group "}}}}; </script>Copy the code

Rendering ๐Ÿ’—

From the above effects we know:

  • Component A listens to all C events because component B’s v-on=$listener throws all C events to the parent component
  • B Component passing$attrsGot all properties of the parent component (except those accepted by props)
  • C passes C the props attribute of the ATTRs object to C through B’s v-bind:$attrs

Note โš  :

  • Component B has already passed all events thrown by component C to the parent component via $listener, but it can still listen to events thrown by component C

Modifying Component B

 <C v-bind="$attrs" v-on="$listeners" @blurHandle="blurHandle"></C>

methods: {
   blurHandle() {
     console.log('B็ป„ไปถ็›‘ๅฌๅˆฐไบ†');
   }
 },

Copy the code

One more point: when component A uses component B, it needs to pass its own parameters as well as obtain the parameters passed by component B. This scenario should be quite numerous

Example: Component A uses component B. Component B passes parameters to its parent by throwing A custom event. Component A listens for custom events and passes its own parameters.

A.vue

<template>
	<B
  	@UpClick="clickHandle($event, 1})"
  />
</template>

methods: {
   clickHandle(ev, myVal) {
   	 console.log(ev, myVal)
   }
 },
Copy the code

B.vue

</button> Methods: {blurHandle(ev) {let params = {ev, data: {testVal: $emit('UpClick', params)}} this.$emit('UpClick', params)}},Copy the code

Let’s continue parsing the dynamic-form-item component source code

Script part

(props, inject, data, filters, components…) (Watch, computed, hook function…) .

import { initData } from "@/api/data";
import TreeSelect from "@/components/tree-select/index.vue";
import FormUpload from "@/components/form-upload"
import { login } from '@/api/user';

components: { TreeSelect, FormUpload },
Copy the code

All of these are extensions to the form, but outside of the login method, we probably won’t use the rest.

Copy the attribute items passed using the dynamic-form-item component.

<dynamic-form-item
  class="item"
  :allDisabled="allDisabled"  false
  :ref="item.key" input
  :item="item"Current configuration item: is an object type, bound object, description will definitely listen :value="value[item.key]" loginForm['username']
  :disabled="disabled"You can configure the disabling of individual forms :style="{'min-width': columnMinWidth }"You can set the minimum width @input= for the label text of the form"handleInput($event, item.key)" 
  @changeSelect="changeSelect"
  @uploadSuccess="uploadSuccess"</dynamic-form-item> </dynamic-form-item>Copy the code

props/inject/data

props: {
  item: {
    type: Object.required: true
  },
  allDisabled: Boolean.disabled: Boolean
},
inject: {
  formThis: { default: {}}// Get the injected dependency from the ancestor component
},
data() {
  return {
    attachmentData: [].// We don't need this
    asyncOptions: []}; },Copy the code

From the properties passed above, the child component passed by the parent component is accepted with props

  • item
  • allDisabled
  • disabled

The $attrs attribute object is all that remains

  • ref
  • value

Note โš  : the style/class attribute $attrs does not exist.

created/watch/computed

Let’s start with the hook function created

created() { if (! this.item.hasOwnProperty("cascade")) this.getAsyncOptions(); },Copy the code

Obviously we didn’t configure this property for the form item, method execution.

I believe you also know and start to wonder what the cascade configuration project is: we will see this later parsing.

GetAsyncOptions method
/** * Get option */
getAsyncOptions() {
  const { optionsUrl, dicType, params } = this.item;
  let data = params;
  if (dicType) {
    data = Object.assign({ dicType: dicType }, data);
  }
  if (optionsUrl) {
    initData(optionsUrl, data)
      .then(res= > {
        if (res.code === 200) {
          this.asyncOptions = res.data.content || res.data;
        }
      })
      .catch(err= > {
        this.$message.error(err.message); }); }},Copy the code

We don’t have any of these properties configured for the form item, so I’m just going to show you the properties in the item

But as you can see from the code above, form items can also be dynamically retrieved by passing a URL, just like the element-UI effect

This code breaks when you get to this point, so look at what’s going on in Watcht and computed

computed: {
  isRange() { // We didn't set the scope
    return this.subtype === "timerange";
  },
  isDisabled() { // This is the form item disabled
    return this.item.disabled || this.disabled; }}Copy the code

When we look at watch, we should know that the current properties must be responsive.

 watch: {
  "item.params": { 
    handler(val, oldVal) {
      if (val === undefined && oldVal === undefined) { // The null value is not processed
        return;
      }
      if (!this.isObjectEqual(val, oldVal)) { // Look at the name should be the method to determine whether the value is an object
        if (this.item.hasOwnProperty("cascade")) {
          this.getAsyncCascadeOptions(val); }}},deep: true.immediate: true}},Copy the code

The getAsyncOptions method is used to configure the form to retrieve data from the server. The params method is used to configure the form to retrieve data from the server

Let’s examine these two methods

  • isObjectEqual
  • getAsyncCascadeOptions

Now you know what the CASCADE configuration project is, and whether the cascade Input item is a dynamic form or not, we will see the internal implementation of getAsyncCascadeOptions

isObjectEqual
  • Obj1: new value for item.params
  • Obj2: the old value of item.params
isObjectEqual(obj1, obj2) {
  const o1 = obj1 instanceof Object;
  const o2 = obj2 instanceof Object;
  if(! o1 || ! o2) {// Determine whether the data is equal if it is not an object
    return obj1 === obj2;
  }
  // Determine the length of the array of enumerable properties of the object
  if (Object.keys(obj1).length ! = =Object.keys(obj2).length) {
    return false;
  }
  for (const attr in obj1) {
    const a1 =
      Object.prototype.toString.call(obj1[attr]) === "[object Object]";
    const a2 =
      Object.prototype.toString.call(obj2[attr]) === "[object Object]";
    const arr1 =
      Object.prototype.toString.call(obj1[attr]) === "[object Array]";
    if (a1 && a2) {
      // If it is an object, continue the judgment
      return this.isObjectEqual(obj1[attr], obj2[attr]);
    } else if (arr1) {
      // If it is an object
      if(obj1[attr].toString() ! == obj2[attr].toString()) {return false; }}else if(obj1[attr] ! == obj2[attr]) {// If the value is not an object, determine whether the value is equal
      return false; }}return true;
}
Copy the code

At first glance it looks a little scary ๐Ÿ˜ฎ, but thanks to the comments, let’s follow the comments to parse this method

Let’s look at the conditionals first

const o1 = obj1 instanceof Object; 
const o2 = obj2 instanceof Object;
if(! o1 || ! o2) {// Determine whether the data is equal if it is not an object
  return obj1 === obj2;
}
// Determine the length of the array of enumerable properties of the object
if (Object.keys(obj1).length ! = =Object.keys(obj2).length) {
  return false;
}
Copy the code

From the above comment, we can see that this is a simple judgment on the dynamic change of params attribute value. The implementation of this method also has some flaws

if(! o1 && ! o2) {// Compare values if both of them are not objects. If one of them is an object, how can you compare values
  return obj1 === obj2;
}
// The risk of prototype contamination was not considered
if (Reflect.ownKeys(obj1).length ! = =Reflect.ownKeys(obj2).length) {
  return false;
}
Copy the code

The logic that loops through the value of an object can be extracted into methods

let isType = (type) = > (obj) = > Object.prototype.toString.call(obj) === `[object ${type.slice(0.1).toUpperCase() + type.slice(1)}] `
let isObject = isType('Object')
let isArray = isType('Array')

function compareObjVal(obj1, obj2) {
  const o1 = isObject(obj1)
  const o2 = isObject(obj2)

  if(! o1 && ! o2) {// Compare values if both of them are not objects. If one of them is an object, how can you compare values
    return obj1 === obj2;
  }

  // The risk of prototype contamination was not considered
  if (Reflect.ownKeys(obj1).length ! = =Reflect.ownKeys(obj2).length) {
    return false;
  }
  
  for (const attr in obj1) {
    if(! obj1.hasOwnProperty(attr))return;
    const a1 = isObject(obj1[attr]) 
    const a2 = isObject(obj2[attr])
    const arr1 = isArray(obj1)
    
    // If it is an object, continue the judgment
    if (a1 && a2) return compareObjVal(obj1[attr], obj2[attr]);
                      
    // If it is an array
    if (arr1) return obj1[attr].toString() === obj2[attr].toString()
                      
    // If the value is not an object, determine whether the value is equal
    if(obj1[attr] ! == obj2[attr])return false;

    return true; }}Copy the code

You can test yourself after you rewrite it

let a = {
  user: {
    name: 'Beige'.msg: 'Welcome to join the front end self-taught post exchange group'
  },
  savoir: [1.2.3.4]};let b = {
  user: {
    name: 'wechat'.age: 'itbeige'
  },
 savoir: [1.2.3.4]}let c = {
  user: {
    name: 'Beige'.msg: 'Welcome to join the front end self-taught post exchange group'
  },
  savoir: [1.2.3.4]}; compareObjVal(a, b)// false
compareObjVal(a, C) // true
Copy the code
getAsyncCascadeOptions
getAsyncCascadeOptions(params) {
const { optionsUrl } = this.item;
  if (optionsUrl) {
    initData(optionsUrl, params)
      .then(res= > {
        if (res.code === 200) {
          this.asyncOptions = res.data.content || res.data;
        }
      })
      .catch(err= > {
        this.$message.error(err.message); }); }}Copy the code

From this approach we know that if you want to configure dynamic input, you need to pass

  • cascade
  • optionsUrl
  • params
  • DicType (optional)

For the extended form-upload, tree-select.. These secondary encapsulation components, interested partners can go down my repository source code down their own research.

  • Address: ๐ŸŒŸ : github.com/it-beige/bo…
  • C9af7b966b03db3cff2113dc9645683af359fdd history version โญ : 3

Finally, let’s comb through what we’ve learned by reading other people’s source code

  • This section describes how to encapsulate modules in common scenarios by configuring items
  • Provide/Inject Application scenarios
  • $attrs/$Listener application scenario, this method can fully achieve the effect of bidirectional binding
  • Listen for child component parameters while passing your own parameter solution

Thank you for being able to read all here, read the source code is a very need to calm down to do a thing, finally give you a sentence I see in a document: patience, not anxious not impatient, although this is not the only element of success, but it is your technology on the basis of going on for a long time.

By the way, welcome to join the exchange group of this project, we grow together! weChat: itbeige

Write in the last

If there is a piece of writing in the article is not very good or there are questions welcome to point out, I will also keep modifying in the following article. I hope I can grow with you as I progress. Those who like my article can also pay attention to it

I’ll be grateful to the first people to pay attention. At this time, you and I, young, packed lightly; And then, rich you and I, full of it.

The articles

ใ€ Front-end system ใ€‘ Talk about understanding EventLoop from an interview question (updated analysis of four advanced questions)

[Front end system] Build a great oaks from the foundation

[Front-end System] The application scenario of re in development is not just rule verification

“Functional programming practical scenario | nuggets technical essay – double festival special articles

ใ€ suggested favorites ใ€‘ CSS obscure points are all here