# widget开发
这里介绍一个从搭建微前端到完成widget开发案例的大概流程。
# 开发前提
本项目的运行依赖afcenter-ui和portal-ui项目+widget-ui项目(部分组件使用到components项目),开发过程中确保项目都在运行。
# 创建微前端项目
1.安装primeton命令
npm install primeton-cli -g
2.创建微前端项目
primeton create-m-app 指令参考:
primeton create-m-app [moduleName] [debugPort]
创建微前端项目myApp,调试端口:8888
primeton create-m-app myApp 8888
运行命令后,在当前工作目录生成微前端项目结构:

# 定义参数
在微前端项目myApp中新建配置文件,eg: src\utils\widgetSetting.js
注:如果开发过程中,涉及修改参数,需要重新拉取widget
export const dropConfig = {
  my_widget: { // 与widget绑定资源code一致
    code: 'my_widget',
    showStyle: 'list',
    eventLists: [], // 根据所需(0个或多个),自定义参数,
    width: 60, // widget的初始宽度,非必填(屏幕总宽分为120个单元,默认值40,60就是一半)
    height: 10, // widget的初始高度,非必填(屏幕总高分为20个单元,默认值8)
    global: [{ // 头部的横向菜单
        label: '清空',
        eventCode: 'add',
        component: 'widget_button'
    }],
    self:[{ // 头部的纵向菜单,按钮以及弹框组件
            label: '新增', // 按钮名称
            eventCode: 'add',
            icon: 'el-icon-plus',
            isDialog: true,
            component: 'widget_form' //自定义事件对应的组件;需在 /views/settingComponents/目录下写的组件name一致
        }]
  }
}
# 暴露模块
- 暴露配置文件
- 并新建配置文件中出现的组件,在build/mfp.config.js中对外暴露页面(开发过程中修改此配置文件,需要重启项目)
exposes: {
    ...,
    // 配置文件
    './settingConfig': './src/utils/widgetSetting.js',
    // widget主页面
    './my_widget': './src/views/my_widget/index.vue',
    // 横向布局组件
    './widget_button': './src/views/my_widget/widget_button.vue',
    // 弹框组件
    './widget_form': './src/views/my_widget/widget_form.vue'
}
# 配置代理
添加微前端的代理
// webpack代理
'/myApp': {
	target: 'http://localhost:8888/',
},
// nginx代理
location /myApp{
    proxy_pass http://localhost:8888/myApp;
}
# 配置页面
在运行项目的<应用管理>页面里注册资源
- 配置微前端模块(添加并保存)

- 添加分组

- 添加页面  
- 页面授权(勾选中,并保存)  
- 添加门户  
- 门户授权 

- 添加widget  
- widget授权(一般添加后会自动授权,如果没有你想要的角色,可自行授权,同门户) 
- 进入门户,并拖拽出widget 


# 开发调试
页面大概拆分为主页面、横向菜单和纵向菜单
要想实现数据持久化,有两种途径,一种是直接调用自备的接口;另一种是通过widget的公共方法实现数据保存。下面的场景将详细介绍后者的实现。

# 主页面实现
页面实现
在页面中,获取到的props参数只有配置中的自定义参数(eventLists: [])
<div class="memo_main">
    <el-row v-for="(event, index) in $attrs.eventLists" :key="event.id">
        <el-divider v-if="index"></el-divider>
        <el-col class="memo_item">
            <div class="memo_title">{{ event.title }}</div>
            <div class="memo_action">
                <el-button type="text" @click="remove(event)">移除</el-button>
            </div>
        </el-col>
        <el-col class="memo_item">
            <div class="mr-20">{{ event.updateTime }}</div>
            <div>{{ event.content }}</div>
        </el-col>
    </el-row>
</div>
数据持久化
实现数据持久化,通过触发updateItem方法,(这里的cloneDeep引入自lodash)
remove(event) {
    this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
    })
    .then(async () => {
        let lists = cloneDeep(this.$attrs.eventLists)
        lists = lists.filter(i => i.id !== event.id)
        this.$emit('updateItem',{eventLists: lists})
    })
    .catch(() => {
    })
}
# 横向菜单(global配置)
页面头部的横向菜单,涉及global配置,整个模块是一个slot,可以在对应的组件中实现所需功能。
页面实现和数据持久化
实现数据持久化,可通过更新configOption数据实现保存
<template>
  <div>
    <el-button @click="clearAll">清空</el-button>
  </div>
</template>
<script>
export default {
  name: 'myWidgetButton',
  props: {
    configOption: {},
  },
  methods:{
    clearAll() {
      const data = {
        ...this.configOption,
        eventLists: [],
        doSave: true
      }
      this.$emit('update:configOption', data)
    }
  }
}
</script>
# 纵向菜单(self配置)
页面头部的纵向菜单,点击打开弹框。涉及self配置,例如<新增>
在self配置项的label展示为下拉菜单,菜单点击事件已在公共方法里处理过,弹框组件为配置项的component值
弹框页面实现如下:
其中页面props参数:dialogVisible(弹框是否展示)、configOption(是整个widget的模型数据)
<div>
    <el-dialog title="添加备忘录" :visible.sync="dialogVisible" width="800px" append-to-body :before-close="handleClose">
        <el-form :ref="formRef" :model="form" :rules="validateRules" label-width="100px">
            <el-row>
                <el-col :span="22">
                    <el-form-item label="标题" prop="title">
                        <el-input v-model="form.title" />
                    </el-form-item>
                </el-col>
                <el-col :span="22">
                    <el-form-item label="内容" prop="content">
                        <el-input v-model="form.content" type="textarea" />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <span slot="footer">
            <el-button @click="handleClose">取 消</el-button>
            <el-button type="primary" @click="submitForm">确 定</el-button>
        </span>
    </el-dialog>
</div>
弹框数据持久化如下
发公共方法,需要修改弹框的dialogVisible = false,这样在$emit('configOption')时会触发数据保存和更新
submitForm(){
	this.$refs[this.formRef].validate(async valid => {
        if (valid) {
            let data = cloneDeep(this.configOption)
            data.dialogVisible = false
            const time = new Date()
            const updateTime = time.getFullYear() + '/' + time.getMonth() + '/' + time.getDate()
            data.eventLists.push({id: `t_${Math.random()*(100000)}`,...this.form, updateTime})
            this.$emit('update:configOption', data)
        }
    })
}
# 编译部署
- 开发完成后,打包部署。在项目根目录下,执行命令
npm run build:prod
- 将打包生成的dist文件放置在widget-ui包同级目录下 
- 添加代理,启动项目 
← 平台/应用widget管理 微前端模式 →