【前端】生产环境部署及性能调优

概述

前端工程使用vue-cli-service进行构建,生成dist静态资源目录,其中包括htmlcssjavascript以及其他项目中使用到的所有资源。

在生产环境中,我们通常使用Nginx开启访问服务,并定位其访问目录至dist目录下的index.html,以此来实现前端工程的访问。

不仅如此,为了使得前端发起请求时,可以正确访问到后端服务,也需要在nginx中配置相应的代理,使得访问过程在同域中进行,以达到Cookie共享的目的。

当然,服务部署的形式可以有多种,上述的部署方式也是较为常用的部署方式。

部署

  • 使用production模式进行打包
  • 使用dotenv-webpack插件启用process.env
  • 配置chainWebpack调整资源加载顺序
  • 使用thread-loader进行打包加速

性能调优

  • 使用compression-webpack-plugin插件进行压缩打包
  • 启用Nginxgzipgzip_static功能
  • 使用OSS加速静态资源访问(可选)

使用production模式进行打包

在package.json中添加执行脚本
{
  "scripts": {
    "build": "vue-cli-service build --mode production"
  }
}
执行打包命令
npm run build

使用dotenv-webpack插件启用process.env

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "dotenv-webpack": "1.7.0"
  }
}
npm install dotenv-webpack@1.7.0 --save-dev
在vue.config.js中添加配置
const Dotenv = require('dotenv-webpack');

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    plugins: [
      new Dotenv()
    ]
  }
};
.env加载顺序

使用不同模式,加载的文件不同。文件按照从上到下依次加载。

  • development

    • .env
    • .env.development
  • production

    • .env
    • .env.production

配置chainWebpack调整资源加载顺序

chainWebpack对资源加载顺序取决于name属性,而不是priority属性。如示例中的加载顺序为:chunk-a --> chunk-b --> chunk-c。

code>test属性决定其打包范围,但集合之间会自动去重。如chunk-a打包node_modules下所有内容,chunk-b打包node_modules/@kunlun下所有内容。因此在chunk-a中将不再包含node_modules/@kunlun中的内容。没有test属性将意味着打包其他内容。

通常情况下,我们希望第三方资源优先加载,@kunlun会覆盖第三方资源(如css样式在同级别选择器中优先级取决于加载顺序),项目中的资源优先级最高。

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  chainWebpack: (config) => {
    config.plugin('html').tap((args) => {
      args[0].chunksSortMode = (a, b) => {
        if (a.entry !== b.entry) {
          // make sure entry is loaded last so user CSS can override
          // vendor CSS
          return b.entry ? -1 : 1;
        } else {
          return 0;
        }
      };
      return args;
    });
    config.optimization.splitChunks({
      ...config.optimization.get('splitChunks'),
      cacheGroups: {
        libs: {
          name: 'chunk-a',
          test: /[\\/]node_modules[\\/]/,
          priority: 1,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        kunlun: {
          name: 'chunk-b',
          test: /[\\/]node_modules[\\/]@kunlun[\\/]/,
          priority: 2,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        common: {
          name: 'chunk-c',
          minChunks: 2,
          priority: 3,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    });
  }
};

使用thread-loader进行打包加速

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "thread-loader": "3.0.4"
  }
}
npm install thread-loader@3.0.4 --save-dev
在vue.config.js中添加配置
const path = require('path');

module.exports = {
  publicPath: '/',
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          include: path.resolve('src'),
          use: [
            {
              loader: 'thread-loader',
              options: {
                workers: 3
              }
            }
          ]
        }
      ]
    }
  }
};

使用compression-webpack-plugin插件进行压缩打包

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "compression-webpack-plugin": "4.0.1"
  }
}
npm install compression-webpack-plugin@4.0.1 --save-dev
在vue.config.js中添加配置
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
    publicPath: '/',
    productionSourceMap: false,
    lintOnSave: false,
    configureWebpack: {
        plugins: [
            new CompressionPlugin({
                algorithm: 'gzip',
                test: /\.js$|\.html$|\.css$/,
                filename: '[path].gz[query]',
                minRatio: 0.8,
                threshold: 10240,
                deleteOriginalAssets: false
            })
        ]
    }
}

启用Nginxgzipgzip_static功能

参考资料
在nginx.conf中添加配置

该配置支持位置:httpserverlocation

http {
    gzip_static         on;
    gzip_proxied        expired no-cache no-store private auth;
    gzip                on;
    gzip_min_length     1024;
    gzip_buffers        32 4k;
    gzip_comp_level     5;
    gzip_vary           on;
    gzip_types          text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    gzip_disable        "MSIE [1-6]\.";
}
常用Nginx配置参考
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    # access_log  /var/log/nginx/host.access.log  main;

    root /opt/pamirs;

    location / {
        try_files $uri $uri/ /index.html;
        index  index.html index.htm;
        expires 1d;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache nginx_cache;
        proxy_cache_valid 200 302 1d;
        proxy_cache_valid 404 10m;
        proxy_cache_valid any 1h;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        if ($request_filename ~* ^.*?.(html|htm|js)$) {
            add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
        }
    }

    location /pamirs {
        proxy_pass          http://127.0.0.1:8091;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /pamirs/openapi {
        proxy_pass          http://127.0.0.1:8092;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

PS:127.0.0.1:8091表示服务端访问地址;127.0.0.1:8092表示服务端EIP访问地址;

使用本地OSSNginx配置参考

静态资源文件包下载

server {
    location /file/upload {
        proxy_pass              http://127.0.0.1:8081;
        proxy_set_header    Host    $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /static {
        alias /opt/pamirs/static;

        add_header Content-Disposition attachment;
        add_header Content-Type application/octet-stream;
    }

    location /css/static {
        alias /opt/pamirs/static;

        add_header Content-Disposition attachment;
        add_header Content-Type application/octet-stream;
    }
}

PS:127.0.0.1:8091表示服务端访问地址;

使用OSS加速静态资源访问(可选)

本文以阿里云为例进行讲解,其他OSS可自行翻阅资料进行配置。

参考资料
在package.json中添加依赖或使用npm安装
{
  "devDependencies": {
    "webpack-aliyun-oss": "0.5.2"
  }
}
npm install webpack-aliyun-oss@0.5.2 --save-dev
在vue.config.js中添加配置,并填入OSS配置
const WebpackAliyunOss = require('webpack-aliyun-oss');

const OSS_URL = 'https//';
const OSS_DIST = "/pamirs/oinone/kunlun"
const OSS_REGION = '';
const OSS_ACCESS_KEY_ID = '';
const OSS_ACCESS_KEY_SECRET = '';
const OSS_BUCKET = '';

const UNIQUE_KEY = Math.random();

module.exports = {
  publicPath: `${OSS_URL}/${OSS_DIST}/${UNIQUE_KEY}/`,
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    plugins: [
      new WebpackAliyunOss({
        from: ['./dist/**/**', '!./dist/**/*.html'],
        dist: `${OSS_DIST}/${UNIQUE_KEY}`,
        region: OSS_REGION,
        accessKeyId: OSS_ACCESS_KEY_ID,
        accessKeySecret: OSS_ACCESS_KEY_SECRET,
        bucket: OSS_BUCKET,
        timeout: 1200000,
        deleteOrigin: true,
        deleteEmptyDir: true,
        overwrite: true
      })
    ]
  }
};

Q/A

为什么需要使用压缩打包方式生成.gz文件?

通过Nginx启用gzip压缩虽然也能达到压缩传输的目的,但gzip模块实际上在每次传输时(非disk cache)都会耗费cpu性能对文件内容进行压缩后再进行传输。

文中对Nginx的优化不仅启用了gzip模块,还启用了gzip_static模块。而gzip_static模块在传输时默认会优先使用.gz已经压缩好的文件直接进行传输。这种方式在支持gzip_static浏览器(如Chrome)上可以大大提高访问性能,并且可以减少服务器压力,综合提高服务器响应能力。

演示文件传输

image.png

使用gzip的响应头

image.png

image.png

使用gzip_static的响应头

image.png

image.png

使用OSS而不是通过Nginx进行静态文件的分发有什么好处?

一般来说,云厂商提供的OSS服务不仅是一个高性能的对象存储服务,搭配云厂商提供的CDN加速服务,可以不限地域的使用云资源,以此来提高页面响应速度。

阿里云的OSS是否也支持gzipgzip_static

支持

附录

下面是在本文中提到的完整配置

package.json
{
  "scripts": {
    "build": "vue-cli-service build --mode production"
  },
  "devDependencies": {
    "compression-webpack-plugin": "4.0.1",
    "dotenv-webpack": "1.7.0",
    "thread-loader": "3.0.4",
    "webpack-aliyun-oss": "0.5.2"
  }
}
vue.config.js

在该配置中,当process.env包含DEPLOY=online时自动使用OSS

const Dotenv = require('dotenv-webpack');
const path = require('path');
const WebpackAliyunOss = require('webpack-aliyun-oss');
const CompressionPlugin = require("compression-webpack-plugin");

let BASE_URL = '/';
const { DEPLOY, OSS_REGION, OSS_DIST, OSS_URL, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET, OSS_BUCKET } = process.env;
const UNIQUE_KEY = Math.random();
switch (DEPLOY) {
  case 'online':
    BASE_URL = `${OSS_URL}${UNIQUE_KEY}/`;
    break;
  default:
    BASE_URL = '/';
}

module.exports = {
  publicPath: BASE_URL,
  productionSourceMap: false,
  lintOnSave: false,
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          include: path.resolve('src'),
          use: [
            {
              loader: 'thread-loader',
              options: {
                workers: 3
              }
            }
          ]
        }
      ]
    },
    plugins: [
      new Dotenv(),
      new CompressionPlugin({
        algorithm: 'gzip',
        test: /\.js$|\.html$|\.css$/,
        filename: '[path].gz[query]',
        minRatio: 0.8,
        threshold: 10240,
        deleteOriginalAssets: false
      }),
      DEPLOY === 'online'
        ? new WebpackAliyunOss({
          from: ['./dist/**/**', '!./dist/**/*.html'],
          dist: `${OSS_DIST}/${UNIQUE_KEY}`,
          region: OSS_REGION,
          accessKeyId: OSS_ACCESS_KEY_ID,
          accessKeySecret: OSS_ACCESS_KEY_SECRET,
          bucket: OSS_BUCKET,
          timeout: 1200000,
          deleteOrigin: true,
          deleteEmptyDir: true,
          overwrite: true
        })
        : () => { }
    ]
  },
  chainWebpack: (config) => {
    config.plugin('html').tap((args) => {
      args[0].chunksSortMode = (a, b) => {
        if (a.entry !== b.entry) {
          // make sure entry is loaded last so user CSS can override
          // vendor CSS
          return b.entry ? -1 : 1;
        } else {
          return 0;
        }
      };
      return args;
    });
    config.optimization.splitChunks({
      ...config.optimization.get('splitChunks'),
      cacheGroups: {
        libs: {
          name: 'chunk-a',
          test: /[\\/]node_modules[\\/]/,
          priority: 1,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        kunlun: {
          name: 'chunk-b',
          test: /[\\/]node_modules[\\/]@kunlun[\\/]/,
          priority: 2,
          chunks: 'initial',
          maxSize: 6000000,
          minSize: 3000000,
          maxInitialRequests: 5
        },
        common: {
          name: 'chunk-c',
          minChunks: 2,
          priority: 3,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    });
  },
  devServer: {
    port: 8080,
    disableHostCheck: true,
    progress: false,
    proxy: {
      '/pamirs': {
        changeOrigin: true,
        target: 'http://127.0.0.1:8080'
      }
    }
  }
};

Oinone社区 作者:张博昊原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/6796.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
张博昊的头像张博昊数式管理员
上一篇 2024年4月19日 pm8:46
下一篇 2024年4月22日 pm2:34

相关推荐

  • 【界面设计器】自定义字段组件实战——表格字段组合展示

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 表格中的一列使用多个字段组合展示。 演示内容:表格中存在两列,【编码】和【基础信息】。将【名称】、【创建时间】、【更新时间】在【基础信息】一列展示。 业务分析及实现思路 从需求来看,我们需要实现一个【组合列】组件,并且该组件允许在【表格】视图中使用。由于【组合列】本身也是一个字段,因此这里需要选择需要组合字段中的其中一个字段作为组件切换的基础字段,比如我们可以选择【名称】字段作为基础字段。 在【组合列】组件的属性面板中,我们需要再自定义一个【组合列配置】组件,用来选择需要将哪些字段进行组合,以及为每个组合提供一些基础配置。 这里需要理解一个基本概念,即【组合列】的属性面板是【组合列配置】的【执行页面】。所有组件的属性面板在【执行页面】时都是【表单】视图。 因此我们可以实现一个【组合列配置】组件,并且该组件允许在【表单】视图中使用。其业务类型使用【文本】,我们在保存配置数据时,可以使用JSON数据结构来存储复杂结构。(这里的实现思路并非是最符合协议设定的,但可以满足绝大多数组件场景) 在【组合列配置】组件中,我们可以允许用户添加/移除组合,并且每个组合有两个属性,【标题】和【字段】。 准备工作 此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 创建时间 createDate 日期时间 否 – 更新时间 updateDate 日期时间 否 – 实现页面效果展示 表格视图 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建组合列组件 创建组合列元件 创建组合列配置组件 创建组合列配置元件 设计组合列元件属性面板 创建compositeConfig字段,并切换至【组合配置】组件。 设计组合列配置元件属性面板 启动SDK工程进行组件基本功能开发 PS:这里由于我们创建了两个组件,因此,将SDK分开下载后,然后将组合列配置SDK中的演示代码(kunlun-plugin/src)移动到组合列SDK中,在同一工程中进行开发,最后只需将相关JS文件和CSS文件上传到组合列组件中即可,组合列配置组件可以不进行上传。这里需要注意的是,上传多个包含相同组件功能的JS文件和CSS文件可能在运行时导致无法正常替换、冲突等问题。 (npm相关操作请自行查看SDK工程中内置的README.MD) 开发步骤参考 打开【表格】视图,将【名称】字段的组件切换为【组合列】组件。 在属性面板中看到【组合列配置】组件,并优先实现【组合列配置】组件。这里的属性面板就是【组合列配置】对应的【执行页面】。 当【组合列配置】组件可以按照预先设计的数据结构正确保存compositeConfig属性时,可以在【组合列】组件中的props定义中直接获取该属性,接下来就可以进行【组合列】组件的开发。 代码实现参考 工程结构 typing.ts export interface CompositeConfig { key: string; label?: string; field?: string; value?: string; } CompositeColumnConfig.vue <template> <div class="composite-column-config"> <oio-form v-for="item in list" :data="item" :key="item.key"> <oio-form-item label="标题" name="label"> <oio-input v-model:value="item.label" /> </oio-form-item> <oio-form-item label="字段" name="field"> <a-select class="oio-select" dropdownClassName="oio-select-dropdown" v-model:value="item.field" :options="fields" /> </oio-form-item> <oio-button type="link" @click="() => removeItem(item)">移除</oio-button> </oio-form> <oio-button type="primary" block @click="addItem">添加</oio-button> </div> </template> <script lang="ts"> import { uniqueKeyGenerator } from '@kunlun/dependencies'; import { WidgetInstance } from '@kunlun/ui-designer-dependencies'; import { OioButton, OioForm, OioFormItem, OioInput } from '@kunlun/vue-ui-antd'; import { Select as ASelect } from 'ant-design-vue'; import { computed, defineComponent, PropType, ref, watch } from 'vue'; import { CompositeConfig } from '../../typing'; export default defineComponent({ name: 'CompositeColumnConfig', components: { OioForm, OioFormItem, OioInput, OioButton, ASelect }, props: { currentInstance: { type:…

    2023年11月1日
    1.0K00
  • 自定义表格支持合并或列、表头分组

    本文将讲解如何通过自定义实现表格支持单元格合并和表头分组。 点击下载对应的代码 在学习该文章之前,你需要先了解: 1: 自定义视图2: 自定义视图、字段只修改 UI,不修改数据和逻辑3: 自定义视图动态渲染界面设计器配置的视图、动作 1. 自定义 widget 创建自定义的 MergeTableWidget,用于支持合并单元格和表头分组。 // MergeTableWidget.ts import { BaseElementWidget, SPI, ViewType, TableWidget, Widget, DslRender } from '@kunlun/dependencies'; import MergeTable from './MergeTable.vue'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'MergeTableWidget' }) ) export class MergeTableWidget extends TableWidget { public initialize(props) { super.initialize(props); this.setComponent(MergeTable); return this; } /** * 表格展示字段 */ @Widget.Reactive() public get currentModelFields() { return this.metadataRuntimeContext.model.modelFields.filter((f) => !f.invisible); } /** * 渲染行内动作VNode */ @Widget.Method() protected renderRowActionVNodes() { const table = this.metadataRuntimeContext.viewDsl!; const rowAction = table?.widgets.find((w) => w.slot === 'rowActions'); if (rowAction) { return rowAction.widgets.map((w) => DslRender.render(w)); } return null; } } 2. 创建对应的 Vue 组件 定义一个支持合并单元格与表头分组的 Vue 组件。 <!– MergeTable.vue –> <template> <vxe-table border height="500" :column-config="{ resizable: true }" :merge-cells="mergeCells" :data="showDataSource" @checkbox-change="checkboxChange" @checkbox-all="checkedAllChange" > <vxe-column type="checkbox" width="50"></vxe-column> <!– 渲染界面设计器配置的字段 –> <vxe-column v-for="field in currentModelFields" :key="field.name" :field="field.name" :title="field.label" ></vxe-column> <!– 表头分组 https://vxetable.cn/v4.6/#/table/base/group –> <vxe-colgroup title="更多信息"> <vxe-column field="role" title="Role"></vxe-column> <vxe-colgroup title="详细信息"> <vxe-column field="sex" title="Sex"></vxe-column> <vxe-column field="age" title="Age"></vxe-column> </vxe-colgroup> </vxe-colgroup> <vxe-column title="操作" width="120"> <template #default="{ row, $rowIndex }"> <!– 渲染界面设计器配置的行内动作 –> <row-action-render :renderRowActionVNodes="renderRowActionVNodes" :row="row" :rowIndex="$rowIndex" :parentHandle="currentHandle" ></row-action-render> </template> </vxe-column> </vxe-table> <!– 分页 –> <oio-pagination :pageSizeOptions="pageSizeOptions" :currentPage="pagination.current"…

    2025年1月9日
    1.2K00
  • 表格新增空行或者按照数据如何复制行

    场景 描述 新增按钮点击后表格出现空行,不带业务数据,需要有行内编辑 如何实现 第一步 在layout目录下新增copyTable组件,组件代码如下 import { BaseElementWidget, SPI, TableWidget, Widget } from '@kunlun/dependencies'; import { OioNotification } from '@kunlun/vue-ui-antd'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'copy-table-row' })) export class CopyTableWidget extends TableWidget { @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB"), {}) private tableCopySub; @Widget.BehaviorSubContext(Symbol("$$TABLE_DELETE_CB")) private tableDeleteSub; @Widget.Reactive() @Widget.Provide() protected get editorMode(): any { return 'manual' } public async copyRowData(row,currentRow) { // 获取vxetable 实例 const tableRef = this.getTableInstance()!.getOrigin(); if (tableRef) { // 有复制未保存数据,如何处理? const insertData = tableRef.getInsertRecords(); if(insertData.length > 0){ OioNotification.warning("警告","请检查未保存数据!") return; } const { row: newRow } = await tableRef.insertAt(row,currentRow) // 插入一条数据并触发校验, 其中字段名称可以替换 await tableRef.setEditCell(newRow, 'city') } } public async deleteRowData(row) { // 获取vxetable 实例 const tableRef = this.getTableInstance()!.getOrigin(); if (tableRef) { // 有复制未保存数据,如何处理? console.log(row, 'remove row') tableRef.remove(row) // 插入一条数据并触发校验 } } async mounted() { super.mounted(); this.tableCopySub.subject.next({copyCb: (row,currentRow) => this.copyRowData(row,currentRow)}) this.tableDeleteSub.subject.next({deleteCb: (row) => this.deleteRowData(row)}) } } 第二步 在action目录下覆盖新增按钮或者复制行按钮;代码如下 import {ActionWidget, ClickResult, ReturnPromise, SPI, Widget} from "@kunlun/dependencies"; @SPI.ClassFactory( ActionWidget.Token({ model: 'resource.k2.Model0000001211', // 替换对应模型 name: 'uiView57c25f66fac9439089d590a4ac47f027' // 替换对应action的name }) ) export class CopyRow extends ActionWidget{ @Widget.BehaviorSubContext(Symbol("$$TABLE_COPY_CB")) private tableCopySub; private tableCopyCb; @Widget.Method() public clickAction(): ReturnPromise<ClickResult> { // 按照某一条数据复制行, 按钮在行内 // let data = JSON.parse(JSON.stringify(this.activeRecords?.[0])); // 复制行删除id // if(data) { // delete…

    2024年7月15日
    1.3K00
  • 如何通过 Oineone 平台自定义视图

    在 Oineone 平台上,自定义视图允许用户替换默认提供的页面布局,以使用自定义页面。本文将指导您如何利用 Oineone 提供的 API 来实现这一点。 默认视图介绍 Oineone 平台提供了多种默认视图,包括: 表单视图 表格视图 表格视图 (左树右表) 详情视图 画廊视图 树视图 每种视图都有其标准的 layout。自定义视图实际上是替换这些默认 layout 的过程。 默认的表单视图 layout <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> 内嵌的的表单视图 layout <view type="FORM"> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> 默认的表格 <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="table" slot="table" slotSupport="field"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </pack> </view> 内嵌的的表格 <view type="TABLE"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" /> </element> </view> 左树右表 <view type="table"> <pack title="" widget="group"> <view type="search"> <element slot="search" widget="search"/> </view> </pack> <pack title="" widget="group"> <pack widget="row" wrap="false"> <pack widget="col" width="257"> <pack title="" widget="group"> <pack widget="col"> <element slot="tree" widget="tree"/> </pack> </pack> </pack> <pack mode="full" widget="col"> <pack widget="row"> <element justify="START" slot="actionBar"…

    2024年4月3日
    1.1K00
  • 自定义视图组件(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4) 什么是视图组件 我们将一个视图中提供数据源的组件称为视图组件。 下面,我们将根据提供的示例布局进行进一步介绍。 示例布局(默认表格视图布局) <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> view: 视图标签;一个视图中的所有组件将共享数据源,视图的数据源通过视图组件进行提供。(在这个示例中,该视图的数据源通过widget="table"(TableWidget)提供) pack: 容器组件标签; element: 通用元素组件标签; xslot:dsl插槽; 根据标签性质,我们可以将这个示例布局进一步简化,只留下我们目前要关注的主要内容。 <view type="TABLE"> <element widget="table" slot="table"> <xslot name="fields" /> </element> </view> 在以上示例布局中,有且仅有一个组件会向视图提供数据源,那就是widget="table"(TableWidget)这个组件。我们接下来将对这个组件进行自定义,以实现业务中所需的列表(List)数据源展示方式。 1 平台组件简介 平台提供的基础组件有如下几种: 组件名称 描述 BaseElement element标签通用组件 BaseElementViewWidget 通用视图组件 BaseElementObjectViewWidget 对象(Object)数据源通用视图组件 BaseElementListViewWidget 列表(List)数据源通用组件 平台提供的内置组件有如下几种:(均使用element标签) 组件名称 标签 视图类型 描述 TableWidget widget="table" TABLE 内置表格组件 FormWidget widget="form" FORM 内置表单组件 DetailWidget widget="detail" DETAIL 内置详情组件 GallertWidget widget="gallery" GALLERY 内置画廊组件 TreeWidget/CardCascaderWidget widget="tree/cardCascader" TREE 内置树/卡片级联组件 我们可以根据业务场景,继承不同的组件,来实现自己的业务场景。在自定义过程中,我们建议尽可能的将逻辑控制在组件内部。如果场景是唯一且确定的,也可以进行一些特殊逻辑处理。 2 场景:实现一个虚拟滚动表格(不使用分页器) 2.1 确定组件名称 widget="VirtualTable" 通过布局设置自定义组件名称 我们将原表格布局中的widget="table"改为我们所需要的自定义组件名称即可。 多个视图可以绑定同一个布局,所以这种修改方式更适用于大范围使用相同布局的情况。 <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar> <xslot name="actions" /> </element> <element widget="VirtualTable" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> 通过DSL设置自定义组件名称 我们使用了slot="table"这个插槽,通过属性合并覆盖的方式,在DSL上直接指定我们所需要的自定义组件名称即可。 这种修改方式更适用于个别几个视图需要使用该组件的情况。 <view type="TABLE"> <template slot="table" widget="VirtualTable"> …… </template> </view> 2.2 简单实现一个基础功能的虚拟滚动表格 定义一个VirtualTable.vue文件,使用平台提供的oio-table组件。目前内部采用vxe-table封装,相关api文档 点击查看 props定义: showDataSource: 当前展示数据;通过平台内置BaseElementListViewWidget组件提供。 <template> <oio-table ref="table" border show-overflow height="400" :row-config="{ isHover: true…

    2023年11月1日
    91900

Leave a Reply

登录后才能评论