Vue3 develops a freely configurable browser start page



HowdzIs based onVue3 + TypescriptDeveloped a fully customized browser navigation start page, which supports adding material components on demand, and can freely edit the location, size and function of components. It supports responsive design and can customize random wallpaper, dynamic wallpaper, etc. Project provisionWeb page online access. packingBrowser plug-in. packingDesktop application (electron)And other access methods.

This paper records the relevant technologies used in the project development.

Form encapsulation

Various material components can be added freely in the project, and each material component contains its own configuration item form, and some of them have the same configuration items, so a JS data-driven form encapsulation can be realized.

Currently usedElementPlusFramework, which encapsulates a standardform component and passes it informDataAndformConfTwo attributes can generate a two-way bound form, which supportsJSXInsert other custom components. Due to the space problem, the component packaging code can be referred to here:standard-form.vue

Then you can use a format similar to JSON to implement the configuration form of each material component, such asWeatherComponentsetting.tsxAs follows:

// @/materials/Weather/setting.tsx
import pick from '../ Base '// pick can freely select a public configuration
export default {
  formData: {
    weatherMode: 1,
    cityName: '',
    animationIcon: true,
    duration: 15,
    position: 5,
    baseFontSize: 16,
    textColor: '#262626',
    textShadow: '0 0 1px #464646',
    iconShadow: '0 0 1px #464646',
    fontFamily: '',
    padding: 10
  Formconf (formdata: record < string, any >) {// pass in formdata to realize bidirectional binding
    return {
      weatherMode: {
        Label: 'weather city',
        type: 'radio-group',
        radio: {
          List: [{Name: 'automatic acquisition (IP)', value: 1}, {Name: 'manual input', value: 2}],
          label: 'name',
          value: 'value'
      cityName: {
        when: (formData: Record<string,any>) => formData. Weathermode = = = 2, // similar to V-IF
        type: 'input',
        Attrs: {placeholder: 'please enter city name (currently only Chinese city name is supported)', clear: true},
        rules: [{
          required: true,
          validator: (rule: unknown, value: string, callback: (e?:Error) => void) => {
            formData. weatherMode === 2 && ! value ?  Callback (new error ('Please enter city name '): callback ()
        }]// support El form native rules
      animationIcon: {
        Label: 'animation icon',
        type: 'switch',
        Tips: 'icon with animation is used by default. If you want to improve performance, you can turn off using static icon'
      duration: {
        Label: 'automatic refresh frequency',
        type: 'input-number',
        attrs: { 'controls-position': 'right', min: 5, max: 12 * 60 }, 
        Tips: 'refresh rate, in minutes'
      ... Pick (formdata, [// select the public configuration

Right click menu

After adding a material component, you can right-click the pop-up menu to change the configuration or delete it in the edit mode. The implementation source of the right-click menu is the same as the author’s open source@howdjs/mouse-menu。 At the same time, in this project, in order to be compatible with the mobile terminal, the plug-in is encapsulated twice, and the function of long press pop-up menu is added. Secondary packaging codeRefer here

Used in the project isVue instructionThe menu plug-in can receive any parameter for callback, so it can transfer the clicked material component data to the callback for various operations.

    <div v-for="element in affix" :key="">
        <div v-mouse-menu="{ disabled: () => isLock, params: element, menuList }">
            <!--Material code-->
setup () {
    const isLock = computed(() => store.state.isLock)
    const menuList = ref([
        {label: 'basic configuration', tips: 'edit base', FN: (params: componentoptions) = > emit ('edit ', params. I)},
        {label: 'Delete', tips: 'Delete', FN: (params: componentoptions) = > store. Commit ('deletecomponent ', params)}
    //Params in FN is component data

Material component layout

Currently, 2 layout methods are provided. One is grid layout based on class file stream, which will arrange components one by one, and the other is fixed layout, which can fix components at any position on the page.

Grid mode

Grid mode usagevue-grid-layoutThe plug-in vue3 version is in beta.

    <grid-layout v-model:layout="list" :col-num="12" :row-height="rowHeight" :is-draggable="!isLock" :is-resizable="!isLock">
        <grid-item v-for="item in list" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i">
            <!--Material code-->
setup () {
    const isLock = computed(() => store.state.isLock)
    const list = computed({
        get: () => store.state.list,
        set: (val) => { store.commit('updateList', val) }

usev-model:layoutBi directional binding grid mode material component list data, because the material array exists in vuex, which is used herecomputedUpdate the setter of.isLockIt is used to judge whether it is currently in editing mode. Dragging and size change are disabled in the locked state. The number of grids currently used is 12, that is, the screen width is divided into 12 copies.

Vue3 develops a freely configurable browser start page

Fixed mode

The fixed mode uses the author’s own open source@howdjs/to-controlAfter the plug-in is completed, you can fix the material components in any position on the page, and you can also drag and drop the lower right corner to change the size.

        v-for="element in affix"
            positionMode: element.affixInfo.mode,
            moveCursor: false,
            disabled: () => isLock,
            arrowOptions: { lineColor: '#9a98c3', size: 12, padding: 8 }
        @todragend="handleAffixDragend($event, element)"
        @tocontrolend="handleAffixDragend($event, element)"
        <!--Material code-->
setup () {
    const isLock = computed(() => store.state.isLock)
    const affix = computed(() => store.state.affix)
    const handleAffixDragend = ($event: any, element: ComponentOptions) => {
        const mode = element.affixInfo?.mode || 1
        const { left, top, bottom, right, width, height } = $event
        const _element = JSON.parse(JSON.stringify(element))
        _element.affixInfo.x = [1, 3].includes(mode) ? left : right
        _element.affixInfo.y = [1, 2].includes(mode) ? top : bottom
        if (width && height) {
            _element.w = width
            _element.h = height
        store.commit('editComponent', _element)

Different from the grid mode, the event callback function is used to update the vuex data of the component. Also useisLockDetermine whether the component is locked. The plug-in supports changing the positioning direction and recording it in the upper right corner, lower right corner, etc., which is very effective for responsive layout. For more usage, please refer to:@howdjs/to-control

Vue3 develops a freely configurable browser start page

Interactive pop-up Popover

The system provides a function of configuring interactive behavior, which can configure the pop-up window of another component when clicking one component, and configure the pop-up direction of the component. After investigation, it is found thatElement-plusofPopoverIt is not suitable for this situation because the pop-up components are dynamic. So it supports not only one component, but also its own configurationPopoverIn all directions, there is another extensionScreenCenterPop up, so that components can pop up in the middle of the screen (similar todialog)。

By passing in the clicked element, the width and height of the target pop-up window and the pop-up window direction, return the information of the target pop-up windowxandy。 The core code is as follows:

 *Get Popover target information
 *@ param element source DOM
 *@ param popaverrect Popover info
 *@ param direction Popover direction
 * @returns [endX, endY, fromX, fromY]
export function getPopoverActivePointByDirection(
  element: HTMLElement,
  popoverRect: PopoverOption,
  direction = DirectionEnum.BOTTOM_CENTER
) {
  const { width, height, top, left } = element.getBoundingClientRect()
  const { width: popoverWidth, height: popoverHeight, offset = 10 } = popoverRect
  const activePointMap = {
    [DirectionEnum.SCREEN_CENTER]: [window.innerWidth / 2 - popoverWidth / 2, window.innerHeight / 2 - popoverHeight / 2],
    [DirectionEnum.TOP_START]: [left, top - popoverHeight - offset],
    [DirectionEnum.TOP_CENTER]: [left + width / 2 - popoverWidth / 2, top - popoverHeight - offset],
    [DirectionEnum.TOP_END]: [left + width - popoverWidth, top - popoverHeight - offset],
    [DirectionEnum.RIGHT_START]: [left + width + offset, top],
    [DirectionEnum.RIGHT_CENTER]: [left + width + offset, top + height / 2 - popoverHeight / 2],
    [DirectionEnum.RIGHT_END]: [left + width + offset, top + height - popoverHeight],
    [DirectionEnum.BOTTOM_END]: [left + width - popoverWidth, top + height + offset],
    [DirectionEnum.BOTTOM_CENTER]: [left + width / 2 - popoverWidth / 2, top + height + offset],
    [DirectionEnum.BOTTOM_START]: [left, top + height + offset],
    [DirectionEnum.LEFT_END]: [left - popoverWidth - offset, top + height - popoverHeight],
    [DirectionEnum.LEFT_CENTER]: [left - popoverWidth - offset, top + height / 2 - popoverHeight / 2],
    [DirectionEnum.LEFT_START]: [left - popoverWidth - offset, top]
  const fromPoint = [left + width / 2, top + height / 2]
  return [...activePointMap[direction], ...fromPoint] || [0, 0, ...fromPoint]

In addition, usetransform-originThis attribute can realize the animation of pop-up window from clicking on elements. Finally, configure the direction of pop-up window and the type of pop-up components. Code reference:ActionPopover.vue

Vue3 develops a freely configurable browser start page

Get any website favicon

stayCollectionAndSearchIn the component, there is a function that the user can automatically obtain the favicon of the website after entering the website. In the first version, the website origin + / favicon is directly used ICO, but after a lot of attempts, it is found that the icons of many websites are not stored in this standard form. So later, I implemented a back-end interface to obtain.

Back end interface principle:

  1. Read the origin from the website entered by the user
  2. Try fromRedisRead the cached icon path in, and return if it is read
  3. If not in the cache, this is usedcheerioLoad website, use$('link[rel*="icon"]').attr('href')Read icon path
  4. If it is not read in the previous step, continue to try to read in the standard form, that is, the website origin + / favicon ico
  5. Write if reading is successfulRedisCache, otherwise, it will return the acquisition failure

Simultaneous interface receptiontypeParameter, which can directly return the picture stream from the back end to solve the CORS restrictions on icon resources of some websites. Because inCollectionIn the component, in order to reduce the number of initial access requests loaded, after reading the icon, the front end will convert the icon into Base64 format and store it in local storage. In this way, you need to use ajax to get the icon and let the interface directly return to the file stream, which can solve the cross domain problem.

In addition, when reading the icon, the front end will buckle the white part of the icon into transparency using the canvas channel method, and the code can beRefer here

Vue3 develops a freely configurable browser start page


The project is still under continuous optimization and development. Various suggestions are welcome. Due to space problems, some of the technologies used will update the records from time to time. If you can continue to pay attention, star, thank you.

Related links