20230701----重返学习-Vue的单向数据流-todoList项目-组件封装-jsx语法

news/2024/11/24 18:40:01/

day-103-one-hundred-and-three-20230701-Vue的单向数据流-todoList项目-组件封装-jsx语法

常见面试题

  • 面试题:怎样理解 Vue 的单向数据流?
  • 面试题:父组件可以监听到子组件的生命周期吗?
  • 面试题:vue中组件和插件有什么区别?
  • 面试题:平时开发中,你有没有封装过公共组件?如果封装过,则简单说一下你当时是怎么考虑的!

Vue的单向数据流

  • 面试题:怎样理解 Vue 的单向数据流?
    • 所谓单向数据流,指的是属性传递是单向的。
      • 父组件可以基于属性把信息传递给子组件 <Child :x="10" title="...">
      • 但是反过来,子组件是无法基于属性把信息传递给父组件的。
    • 我个人理解的单向数据流,应该还包含:父子组件的钩子函数触发时机,也是遵循单向数据深度优先原则的。
      • 第一次渲染:

        • 完整流程:父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 父组件开始渲染DOM --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件开始渲染DOM --> 子组件结束渲染DOM --> 子组件mounted --> 父组件结束渲染DOM --> 父组件mounted
        • 具体步骤-根据组件中的钩子函数:
          1. 父组件渲染前期:父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 父组件开始渲染DOM -> 下一阶段。
          2. 子组件渲染阶段:–> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件开始渲染DOM --> 子组件结束渲染DOM --> 子组件mounted -> 下一阶段。
          3. 父组件渲染后期: --> 父组件结束渲染DOM --> 父组件mounted
      • 组件更新:

        • 完整流程:父组件beforeUpdate -> 父组件开始更新 -> 子组件beforeUpdate -> 子组件开始更新 -> 子组件结束更新 -> 子组件updated -> 父组件结束更新 -> 父组件updated
        • 具体步骤-根据组件中的钩子函数:
          1. 父组件更新前期:父组件beforeUpdate -> 父组件开始更新 -> 下一阶段。
          2. 子组件更新阶段: -> 子组件beforeUpdate -> 子组件开始更新 -> 子组件结束更新 -> 子组件updated -> 下一阶段。
          3. 父组件更新后期: -> 父组件结束更新 -> 父组件updated
      • 组件销毁:

深度优先和广度优先

  • 深度优先和广度优先

    let obj = {x: 10,y: {z: 20,n: {m: 30,},k: 50,},h: 40,
    };
    
    • 深度优先

      let obj = {x: 10,y: {z: 20,n: {m: 30,},k: 50,},h: 40,
      };
      // x --> y --> z --> n --> m --> k --> h
      // x --> y --> y是对象,进入y --> y.z --> y.n --> y.n是对象,进入y.n --> y.n.m --> y.n对象结束,跳出y.n --> k --> y对象结束,跳出y --> h
      // x --> y --> y.z --> y.n --> y.n.m --> y.k --> h
      
    • 广度优先

      let obj = {x: 10,y: {z: 20,n: {m: 30,},k: 50,},h: 40,
      };
      // x --> y --> h --> z --> n --> k --> m
      // 第一层:[x --> y --> h] --> 第二层:[z --> n --> k] --> 第三层:[m]
      // x --> y --> h --> y.z --> y.n --> y.k --> y.n.m
      

父组件与子组件生命周期

  • 面试题:父组件可以监听到子组件的生命周期吗?
    • 父组件想监测到子组件的钩子函数触发,大体上有两种方案:
      1. 发布订阅:

        • 父组件向子组件事件池中注入自定义事件。
        • 子组件在指定的钩子函数触发时,通知自定义事件执行即可。
        • 代码示例:
          • fang/f20230701/day0701/src/views/Parent.vue父组件:

              <Child @md="childMounted" />methods: {childMounted() {console.log(`子组件第一次渲染完毕了`);},},
            
            <template><div class="parent-box"><Child @md="childMounted" /></div>
            </template><script>
            import Child from "./Child.vue";
            export default {components: {Child,},methods: {childMounted() {console.log(`子组件第一次渲染完毕了`);},},
            };
            </script><style lang="less" scoped>
            .parent-box {box-sizing: border-box;position: relative;margin: 20px auto;width: 200px;height: 200px;background: lightblue;
            }
            </style>
            
          • fang/f20230701/day0701/src/views/Child.vue子组件:

              mounted() {this.$emit("md");},
            
            <template><div class="child-box"></div>
            </template><script>
            export default {mounted() {this.$emit("md");},
            };
            </script><style lang="less" scoped>
            .child-box {box-sizing: border-box;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 100px;height: 100px;background: lightcoral;
            }
            </style>
            
          • 支持传递实参:

            • 代码示例:
              • fang/f20230701/day0701/src/views/Parent.vue父组件:

                <Child @md="childMounted" />
                methods: {childMounted(childBtn) {console.log('子组件第一次渲染完毕了',childBtn)}
                }
                
                <template><div class="parent-box"><Child @md="childMounted" /><!-- <Child @hook:mounted="childMounted" /> --></div>
                </template><script>
                import Child from "./Child.vue";
                export default {components: {Child,},methods: {childMounted(...params) {console.log(`子组件第一次渲染完毕`,params);},},
                };
                </script>
                
              • fang/f20230701/day0701/src/views/Child.vue子组件:

                mounted() {this.$emit('md',this.$refs.btn)
                }
                
                <template><div class="child-box"><button ref="btn">子组件按钮</button></div>
                </template><script>
                export default {mounted() {this.$emit("md",this.$refs.btn);},
                };
                </script>
                
      2. 直接基于@hook:钩子函数监听即可。

        <Child @hook:mounted="childMounted" />
        
        • 代码示例:

          • 父组件:

            <Child @hook:mounted="childMounted" />
            methods: {childMounted() {console.log(`子组件第一次渲染完毕`);},
            }
            
            <template><div class="parent-box"><Child @hook:mounted="childMounted" /></div>
            </template><script>
            import Child from "./Child.vue";
            export default {components: {Child,},methods: {childMounted(...params) {console.log(`子组件第一次渲染完毕`,params);},},
            };
            </script>
            
          • 子组件:

            <template><div class="child-box"><button ref="btn">子组件按钮</button></div>
            </template><script>
            export default {
            };
            </script>
            
          • 不支持传递实参:

            • 父组件:

              <Child @hook:mounted="childMounted" />
              methods: {childMounted(...params) {console.log(`子组件第一次渲染完毕`,params);//`子组件第一次渲染完毕` [];},
              }
              
        • 虽然第二种方式比较简单,但是第一种发布订阅的模式,其支持:给父组件的方法传递实参(可以是子组件中的一些内容),所以平时开发中,需要传参则采用第一种,不需要则采用第二种即可。

todoList项目

  • 项目创建:
  • 项目代码:
    • fang/f20230701/day0701/src/views/TodoList.vue父组件

      <template><div class="todo-box"><div class="handle"><el-input placeholder="请输入任务描述" v-model.trim="text" /><el-button type="primary" @click="submit()">新建任务</el-button></div><list-itemv-for="item in list":key="item.id":info="item"@handle="handle"/><!-- <list-item /><list-item /><list-item /> --></div>
      </template><script>
      // 全局引入了element-ui,而this.$message是element-ui导入并注册的。
      import ListItem from "../components/ListItem.vue";
      import _ from "@/assets/utils";
      /* _.storage为下方代码:
      // 具备有效期的LocalStorage存储
      const storage = {set(key, value) {localStorage.setItem(key,JSON.stringify({time: +new Date(),value,}));},get(key, cycle = 2592000000) {cycle = +cycle;if (isNaN(cycle)) cycle = 2592000000;let data = localStorage.getItem(key);if (!data) return null;let { time, value } = JSON.parse(data);if (+new Date() - time > cycle) {storage.remove(key);return null;}return value;},remove(key) {localStorage.removeItem(key);},
      }; */
      export default {components: {ListItem,},data() {// 组件第一次渲染:先从本地中获取已有的任务列表。let cache = _.storage.get("TODO_CACHE");return {//任务列表;list: cache || [],//任务框中输入的内容。text: "",};},methods: {submit() {// 验证text是否为空。if (this.text.length === 0) {this.$message.warning(`任务描述不可为空哦~`);return;}// 新增任务。this.list.push({id: +new Date(),text: this.text,});this.text = "";},// 修改或删除任务。handle(type, id, text) {// type:操作类型 delete/update// id:要删除/修改任务项的编号。// text:如果是修改操作,text存储的是要修改的信息。if (type === "delete") {this.list = this.list.filter((item) => {return +item.id !== +id;});return;}if (type === "update") {this.list = this.list.map((item) => {if (+item.id === +id) {item.text = text;}return item;});}},},// 监听任务列表的变化,把最新的信息存储到本地。watch: {list: {deep: true,handler() {_.storage.set("TODO_CACHE", this.list);},},},
      };
      </script><style lang="less" scoped>
      .todo-box {box-sizing: border-box;margin: 50px auto;width: 400px;.handle {padding-bottom: 20px;border-bottom: 1px dashed #ddd;display: flex;justify-content: space-between;align-items: center;.el-button {margin-left: 20px;}}
      }
      </style>
      
    • fang/f20230701/day0701/src/components/ListItem.vue子组件

      <template><div class="item-box" v-if="info"><div class="content"><el-input size="mini" v-if="isUpdate" v-model="copyText" /><span class="textCon" v-else>{{ copyText }}</span></div><div class="handle"><el-popconfirm title="您确定要删除本条任务吗?" @confirm="removeHandle"><el-button type="danger" size="mini" slot="reference">删除</el-button></el-popconfirm><el-buttontype="success"size="mini"v-if="!isUpdate"@click="triggerUpdate">修改</el-button><template v-else><el-button type="success" size="mini" @click="saveUpdate">保存</el-button><el-button type="info" size="mini" @click="cancelUpdate">取消</el-button></template></div></div>
      </template><script>
      export default {// 注册接收属性。props: {info: {type: Object,required: true,},},// 定义状态;data() {return {isUpdate: false,copyText: this.info.text,};},//定义操作的方法:methods: {//删除任务。removeHandle() {// 把父组件中存在的某条任务删除。this.$emit("handle", "delete", this.info.id);},// 触发修改操作。triggerUpdate() {this.isUpdate = true;},// 保存修改的信息。saveUpdate() {if (this.copyText.length === 0) {this.$message.warning(`任务描述不能为空哦~`);return;}// 把父组件中存在的某条任务进行修改。this.$emit("handle", "update", this.info.id, this.copyText);this.isUpdate = false;},// 取消修改操作。cancelUpdate() {this.isUpdate = false;this.copyText = this.info.text;},},
      };
      </script><style lang="less" scoped>
      .item-box {margin: 15px 0;.content {margin-bottom: 5px;.textCon {line-height: 30px;font-size: 14px;}.el-input {width: 200px;}}.handle {.el-button {margin-right: 10px;margin-left: 0;}}
      }
      </style>
      

组件封装

  • 在组件化开发的模式下,有一个非常重要的知识:如何抽离封装通用的组件!
  • 一般我们封装的组件,按照特点可以分为:
    • 业务组件-针对于特定的项目,包含一定的业务逻辑:
      • 普通业务组件:
        • 在SPA单页面应用中,每一个路由页面都是一个组件。
        • 一个页面内容比较多,我们开发的时候,把其拆分成多个组件-这些组件可能没有复用性,最后合并渲染。
      • 通用业务组件:
        • 封装的组件会在很多地方用到(比如:推荐列表、新闻列表、回退按钮…)
    • 功能组件-不单纯针对某一个项目,而是适用于很多项目:
      • UI组件库中提供的组件都是功能组件。
        • 我们平时开发的时候,会结合当下的业务需求,对这些组件进行二次封装。
          • 例如:button组件设置loading防抖效果(比如点击事件执行时,自动有loading效果)、Table表格+筛选或分页等的二次封装、骨架屏的二次封装(样式修改及结构简化)。
      • 我们还会自己封装一些UI组件库不具备的组件或者使用第三方插件。
        • 例如:大文件切片上传和断点续传、pdf或word或excel的预览、富文本编辑器、复杂的轮播图效果!
  • 但是不论封装什么类型的组件,最核心的思想:让组件具备更强的复用性,支持更多效果的实现!
    • 首先,我们要改变思想观念:开发项目之前,首先分析那些东西是有类似的部分,需要进行封装提取的!
      • 可能是把几个组件合并在一起,变为一个完整的通用组件。
      • 也可能仅仅是调整一些样式,变为和项目风格统一的效果。
      • 还可能是在原有组件的基础上,扩充一些单独的功能。
      • 当然最主要的还是:包含结构、样式、功能,并在别人使用的时候可以通过传递不同的信息,实现不同的效果。
    • 如何让组件具备更强的复用性:
      • 基于:属性、插槽、自定义事件、实例(拿到组件实例,就可以调用实例上暴露的方法)。
      • 多参考相似的案例需求,进行归纳总结,在封装的时候,让其具备更多的不确定性。
        • 更多的不确定性也就是更多的各种合理属性和插槽,用户可以选择一些属性来定制的不同的效果。
  • 我们封装的组件,有不同的调用方式:
    • 直接在视图中调用渲染 <el-button></el-button>;
      • 封装组件;
      • 基于Vue.component()注册为全局组件;
    • 基于某些方法的执行进行渲染 this.$message.success('...');
      • 封装组件;
      • 基于Vue.extend()处理。
  • 封装组件的时候,我们基本上都使用<template>语法来构建视图,但是其具备弱编程性-即不灵活,此时我们可以基于强编程性jsx语法,来替代<template>语法

封装公共组件

  • 面试题:平时开发中,你有没有封装过公共组件?如果封装过,则简单说一下你当时是怎么考虑的!
    • 自己思考。

代码片断

封装loading防抖按钮

  • 参考来源:

    • Button按钮-文档说明
    • element-ui的Button按钮对应源码在/node_modules/element-ui/packages/button/src/button.vue
  • 未封装前:

    • fang/f20230701/day0701/src/views/Demo1.vue

      <template><div class="demo-box"><el-button type="danger" :loading="deleteLoading" @click="handleDelete">删除</el-button><el-button type="primary" :loading="updateLoading" @click="handleUpdate">修改</el-button></div>
      </template><script>
      /* this.$API.query为
      const query = (interval = 1000) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve({code: 0,message: "ok",});}, interval);});
      }; */
      export default {name: "Demo",data() {return {deleteLoading: false,updateLoading: false,};},methods: {async handleDelete() {this.deleteLoading = true;try {let { code } = await this.$API.query(2000);if(code===0){this.$message.success(`恭喜你,删除成功!`)}else{this.$message.error(`删除失败,请稍后再试!`)}} catch (error) {console.log(`error:-->`, error);}this.deleteLoading = false;},async handleUpdate(){this.updateLoading = true;try {let { code } = await this.$API.query(2000);if(code===0){this.$message.success(`恭喜你,修改成功!`)}else{this.$message.error(`修改失败,请稍后再试!`)}} catch (error) {console.log(`error:-->`, error);}this.updateLoading = false;}},
      };
      </script><style lang="less" scoped>
      .demo-box {box-sizing: border-box;margin: 20px auto;padding: 20px;width: 200px;border: 1px solid lightcoral;.el-button {display: block;margin-bottom: 20px;margin-left: 0;}
      }
      </style>
      
  • 简单的封装:

    1. 创建一个组件:

      • fang/f20230701/day0701/src/components/ButtonAgain.vue
        • ButtonAgain组件:
          • 使用方式需要和ElButton保持一致。
          • 只不过loading效果,组件内部处理好即可!
        • 别人的代码:Vue2进阶/day0701/src/components/ButtonAgainTemplate.vue
    2. 在入口文件处引入,并全局注册:

      • fang/f20230701/day0701/src/main.js或fang/f20230701/day0701/src/global.js,因为global.js是在入口文件main.js直接引入的,和在入口文件执行代码差不多。

        import ButtonAgain from "./components/ButtonAgain.vue";
        Vue.component(ButtonAgain.name, ButtonAgain);
        
    3. 在需要用到该按钮的地方直接使用。

      <template><button-again type="danger" plain size="small" @click="handleDelete" ref="AA">删除</button-again>
      </template>
      
      • fang/f20230701/day0701/src/views/Demo2.vue

        <template><div class="demo-box"><button-again type="danger" plain size="small" @click="handleDelete" ref="AA">删除</button-again><button-again type="primary" circle icon="el-icon-edit" @click="handleUpdate"></button-again></div>
        </template><script>
        /* //this.$API.query为:const query = (interval = 1000) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve({code: 0,message: "ok",});}, interval);});};
        */
        export default {name: "Demo",methods: {async handleDelete() {try {let { code } = await this.$API.query(2000);if (code === 0) {this.$message.success(`恭喜你,删除成功!`);} else {this.$message.error(`删除失败,请稍后再试!`);}} catch (error) {console.log(`error:-->`, error);}},async handleUpdate() {try {let { code } = await this.$API.query();if (code === 0) {this.$message.success(`恭喜你,修改成功!`);} else {this.$message.error(`修改失败,请稍后再试!`);}} catch (error) {console.log(`error:-->`, error);}},},mounted () {console.log(`this.$refs.AA-->`, this.$refs.AA);}
        };
        </script><style lang="less" scoped>
        .demo-box {box-sizing: border-box;margin: 20px auto;padding: 20px;width: 200px;border: 1px solid lightcoral;.el-button {display: block;margin-bottom: 20px;margin-left: 0;}
        }
        </style>
        
    • 处理思路步骤:
  • 封装重构-jsx语法:

    1. 创建一个组件:

      • fang/f20230701/day0701/src/components/ButtonAgain.vue

        <script>
        export default {name: "ButtonAgain",inheritAttrs: false,data() {return {loading: false,};},methods: {async handle(ev) {this.loading = true;try {await this.$listeners.click(ev);} catch (err) {console.log("ButtonAgain Error:", err.message);}this.loading = false;},},mounted() {this.ElButtonIns = this.$refs.child;},render() {// 传递属性的筛选let attrs = {},area = ["type","size","icon","nativeType","disabled","plain","autofocus","round","circle",];Object.keys(this.$attrs).forEach((key) => {if (!area.includes(key)) return;attrs[key] = this.$attrs[key];});return (<el-button{...{ attrs }}loading={this.loading}vOn:click={this.handle}ref="child">{this.$slots.default}</el-button>);},
        };
        </script><style lang="less" scoped></style>
        
    2. 在入口文件处引入,并全局注册:

      • fang/f20230701/day0701/src/main.js或fang/f20230701/day0701/src/global.js,因为global.js是在入口文件main.js直接引入的,和在入口文件执行代码差不多。

        import ButtonAgain from "./components/ButtonAgain.vue";
        Vue.component(ButtonAgain.name, ButtonAgain);
        
    3. 在需要用到该按钮的地方直接使用。

      <template><button-again type="danger" plain size="small" @click="handleDelete" ref="AA">删除</button-again>
      </template>
      
      • fang/f20230701/day0701/src/views/Demo2.vue

        <template><div class="demo-box"><button-againtype="danger"plainsize="small"@click="handleDelete"ref="AA">删除</button-again><button-againtype="primary"circleicon="el-icon-edit"@click="handleUpdate"></button-again></div>
        </template><script>
        export default {name: "Demo",methods: {async handleDelete() {try {let { code } = await this.$API.query(2000);if (+code === 0) {this.$message.success("恭喜您,删除成功!");} else {this.$message.error("删除失败,请稍后再试!");}} catch (_) {}},async handleUpdate() {try {let { code } = await this.$API.query();if (+code === 0) {this.$message.success("恭喜您,修改成功!");} else {this.$message.error("修改失败,请稍后再试!");}} catch (_) {}},},mounted() {console.log(this.$refs.AA);},
        };
        </script><style lang="less" scoped>
        .demo-box {box-sizing: border-box;margin: 20px auto;padding: 20px;width: 200px;border: 1px solid lightcoral;.el-button {display: block;margin-bottom: 20px;margin-left: 0;}
        }
        </style>
        

对于一个组件

  • 对于一个组件:
    • 从技术角度来讲。
      1. 调用方式来决定是jsx还是template语法。
      2. 决定属性、自定义事件、
    • 从思维角度上:
      1. 观察通用性,查看需求及相似的例子。
      2. 普通业务组件、通用业务组件、UI组件库二次封装、第三方插件。

jsx语法

  • 纯h函数创建:

    <script>
    export default {name: "Demo",data() {return {title: "Vue视图构建语法",level: 1,};},/*https://v2.cn.vuejs.org/v2/guide/render-function.htmlrender函数:基于JSX语法构建视图 + h:createElement*/render(h) {return h(`h${this.level}`,{style: {color: "red",},},[this.title,h("span", {}, [100]),h("el-button",{props: {type: "primary",},},["哈哈"]),]);},
    };
    </script>
    
  • jsx语法与h函数:

    <script>
    export default {name: "Demo",data() {return {title: "Vue视图构建语法",level: 1,};},methods: {handle() {this.level = 2;},},render(h) {let styObj = {color: "red",};return h(`h${this.level}`, { style: styObj }, [this.title,// https://github.com/vuejs/jsx-vue2<span vOn:click={this.handle}>100</span>,<el-button type="primary">哈哈哈</el-button>,]);},
    };
    </script>
    

进阶参考

  1. Button 按钮

http://www.ppmy.cn/news/680971.html

相关文章

人脸识别笔记

人脸识别笔记 参考资料&#xff1a; 人脸识别技术看这一篇就够了&#xff08;附国内人脸识别20强公司&#xff09;人脸识别的十个关键技术组成及原理人脸识别技术百度百科人脸识别技术及应用&#xff0c;了解一下深度干货&#xff01;一文读懂人脸识别技术(建议收藏)人脸识别…

下载人脸认证助手_认证助手最新版

认证助手手机软件是一款便民利民的好用服务项目运用&#xff0c;是朝向于各省市县市内农保工资待遇领到工作人员打造出的验证辅助软件。在认证助手最新版本客户们可在线投诉提议&#xff0c;开展身份信息和人脸比对服务项目等。 基本简介 认证助手APP是由金惠高新科技产品研发的…

下载人脸认证助手_认证助手最新版app_认证助手怎样认证步骤_下载人脸认证助手-多特软件站安卓网...

认证助手手机软件是一款便民利民的好用服务项目运用&#xff0c;是朝向于各省市县市内农保工资待遇领到工作人员打造出的验证辅助软件。在认证助手最新版本客户们可在线投诉提议&#xff0c;开展身份信息和人脸比对服务项目等。 基本简介 认证助手APP是由金惠高新科技产品研发的…

6.29 环境配置

anaconda在Linux中直接用wget装&#xff0c;方便。不需先下载.sh文件然后复制到vscode中 如何在Linux服务器上安装Anaconda&#xff08;超详细&#xff09;_linux anaconda_流年若逝的博客-CSDN博客 将所有的实验代码打包放到experiments.zip里面 换源 pip freeze > requi…

解决Vite中引入Naive UI后找不到“date-fns/esm/index.js“模块的方法

在使用Vite构建项目时&#xff0c;有时我们会遇到引入Naive UI组件库后出现找不到"date-fns/esm/index.js"模块的问题。这可能是由于Vite对ES模块的路径解析方式不同导致的。下面将介绍如何解决这个问题。 首先&#xff0c;我们需要了解一些背景知识。Vite是一个基于…

kk6.0 服务器信息 端口,KK的服务器改了端口以后 为什么我进不去

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Connecting to public(122.224.6.123:30010) Sending UDP connect to public IP 122.224.6.123:30010 Retrying public(122.224.6.123:30010) Sending UDP connect to public IP 122.224.6.123:30010 Retrying public(122.224.6.1…

行业领先!5G投资1100亿! 20个中国移动5G成功案例分享( 业绩+案例 )

本篇文章分为两大部分&#xff1a; 第一部分小编将为你揭开中国移动刚刚发布的2020年年度业绩报告。第二部分为中国移动的20个5G典型应用案例。 一、业绩报告 刚刚&#xff0c;中国移动发布2020年年度业绩报告。报告显示&#xff0c;2020年中国移动营运收入为人民币7681亿元&am…

为什么被用户牵着鼻子走?

最近和一位曾经在西门子、AB干过的行业人士&#xff08;以下称&#xff1a;L总&#xff09;交流&#xff0c;号称手上有些资源。我把团体情况、做过的案例、系统产品、能干什么事给L总简要的进行了介绍。L总说&#xff1a;介绍的过于IT化了&#xff0c;现场OT人员更关注如何解决…