vue3:Table组件动态的字段(列)权限、显示隐藏和左侧固定

news2025/6/7 0:03:53

 效果展示

根据后端接口返回,当前登录用户详情中的页面中el-table组件的显示隐藏等功能。根据菜单id查询该菜单下能后显示的列。

后端返回的数据类型:

接收到后端返回的数据后处理数据结构. Table组件文件

<!-- 自己封装的Table组件文件 -->
onMounted(()=>{
//判断页面传递过来的数据对象:PropTableS中keySrequest是否为true;为true则请求后端的table列数据,否则使用自己定义的。(因为还没有定义所有页面的 列字段权限。所有得区分开来,不然其他页面在后端中不存在数据,table显示都会是空的)
    if(props.PropTableS.keySrequest){
    tablekey()
 }
})

onActivated(() => {
//如果不是第一次进入这个页面,可以直接拿之前第一次进入保存的数据.
  if(props.PropTableS.keySrequest &&  proxy.$router.currentRoute.value.meta.key){
    TabKys = proxy.$router.currentRoute.value.meta.key
  }
})



//请求后端数据,处理数据格式
function tablekey(){
    proxy.$axios.get('/system/user/list_fields', {
      permission_id:proxy.$router.currentRoute.value.meta.id
    }).then((res) => {
      let keycopy = JSON.parse(JSON.stringify(TabKys))
      let list = {}
      res.data.map(item=>{
        list[item.code] = {
          title :item.name, //列名
          left_fixed:item.left_fixed == 1 ? true : false, //是否固定
          fixed:item.left_fixed == 1 ? 'left' : false, //默认固定左侧
          id:item.id, //字段id
          sort:item.sort, //排序
          status:item.status == 1 ? true : false, //显示/隐藏
          isshow:item.isshow, //也是显示/隐藏,这个可以在自定义的数据中传递过来,因为之前的页面都是用的isshow,后端用的是status,前面页面太多不想改了。
          width:keycopy[item.code].width, //自定义传递过来的列宽度
          type:keycopy[item.code].type, //自定义传递过来的列类型,如('text','selelct','input'等等)
          permission_id:proxy.$router.currentRoute.value.meta.id//提交给后端的菜单id。
        }
      })
      //TabKys :渲染el-table组件的列对象数据  list:处理后端数据.
      TabKys = list
      //此处理是将进入页面请求后的数据保存,可以避免切换页面,丢失当前页面的table列。出现问题的场景(进入页面A,table组件列正常显示,切换到页面B后,在回到页面A。页面A没有列的名称了。因为路由设置页页面记住缓存。)
      proxy.$router.options.routes[1].children.forEach(route=>{
        route.meta.id == proxy.$router.currentRoute.value.meta.id ?  route.meta.key == undefined ?  route.meta.key = TabKys : '' : ''
      })
      tablekeyindex.value++
    });
}

页面中定义的数据:

页面中定义的table数据

let PropTableS = reactive({ 
  tableStyle: { //table的css
    width: "99%",
    margin: "auto",
  },
  keySrequest:true, //是否需要后端数据渲染
  //自己定义的列的数据 
  keyS: proxy.$PublicAPI.SetTableCotentWidth({ 
    //这不是一个完整列对象数据,因为其他值都在后端返回了。这里的意义就是可以自定义列的类型。后端中没有定义当前列类型字段,也可以不需要后端保存,可以自定义,
    danjuleixing: {
      type:'select' //类型:下拉
      widht:'120px' //定义的宽度 
    },
    status: {
      type:'select'
    },
    
  }),
});

Table组件中右侧定义设置的组件(小齿轮)

<template>
  <div class="Drawer">
    <el-drawer
      z-index="12"
      :modal="false"
      :before-close="beforeClose"
      append-to="el-main"
      class="drawerBox"
      v-model="Props.DrawerObject.visible"
      :close-on-click-modal="false"
      :destroy-on-close="true"
      :show-close="false"
    >
      <template #header="{ close, titleId, titleClass }">
        <div class="deawerTitle">
          <span>表单固定设置</span>
          <span>
            <Icons @click="close" :theme="'outline'" :size="20" :is="'close'"
          /></span>
        </div>
      </template>
      <div class="deawerScanner">
        <el-input
          v-model="input2"
          @input="handelInput"
          class="deawerScanner_input"
          size="small"
          placeholder="搜索关键词"
          :prefix-icon="Search"
        />
      </div>
      <div>
        <el-tabs
          v-model="TabactiveName"
          class="demo-tabs"
          @tab-click="handleTableActiveClick"
        >
          <el-tab-pane label="固定列" name="0"></el-tab-pane>
          <el-tab-pane label="隐藏/显示" name="1"></el-tab-pane>
        </el-tabs>
      </div>
      <div class="deawerTop" :style="{ height: TopHeght + 'px' }">
        <div class="deawerTop_All">
          <el-checkbox
            @click="handelCheack"
            v-model="checked4"
            label="全部固定"
          />
        </div>
        <div>
          <FromData
            ref="From"
            :key="formkey"
            @Drawer_switchChange="Drawer_switchChange"
            class="FromDataDom"
            :fromlist="fromlist"
            style=""
            :style="{ 'margin-top': '0px', height: domHeight + 'px' }"
          ></FromData>
        </div>
        <div class="botton_but">
          <Button
            plain
            :title="'取消'"
            @click="ButtonClick('')"
            :pattern="'centre'"
          />
          <Button
            :plain="fromlist.scannerIsPlain"
            :type="'primary'"
            :title="'保存'"
            @click="ButtonClick('save')"
            :pattern="'centre'"
          />
        </div>
      </div>
    </el-drawer>
  </div>
</template>

<script lang="ts" setup>
import {
  ref,
  reactive,
  onMounted,
  defineProps,
  watch,
  onActivated,
  getCurrentInstance,
  onBeforeUnmount,
  onUnmounted,
} from "vue";
import { Search } from "@element-plus/icons-vue";
import { FromData, Drawer, Icons, Button } from "@/components";
let Props = defineProps(["DrawerObject", "primarytablelist"]);
import { ElMessage, ElMessageBox } from "element-plus";
const primarytablelist = Props.primarytablelist;
const { proxy } = getCurrentInstance();
const From = ref(null);
let fromlist = reactive({});
let input2 = ref("");
let checked4 = ref(false);
let isEmit = ref(true);
let formkey = ref(0);
let TabactiveName = ref("0");
let domHeight = ref(0);
let TopHeght = ref(0);
onMounted(() => {
  GelTableTitleUpdate(Props.DrawerObject.TabKys);
  window.addEventListener("resize", resizeFun);
  setTimeout(() => {
    resizeFun();
  }, 1);

});

onUnmounted(() => {
  proxy.$bus.emit("addEvent", false);
});

function resizeFun() {
  domHeight.value =
    document.querySelector(".drawerBox").offsetHeight - 41 - 39 - 54 - 84 - 50;
  TopHeght.value =
    document.querySelector(".drawerBox").offsetHeight - 41 - 39 - 64;
}

function GelTableTitleUpdate(obj: object) {
  let index = 0;
  let objlength = 0;
  for (const key in obj) {
    if (key != "operate" && key != "selection" && key != "index") {
      objlength++;
      obj[key].type = "switch";
      obj[key].icon = "drag";
      if (obj[key].fixed) {
        index++;
        obj[key].value = true;
      } else if (obj[key].value == "" || obj[key].value == false) {
        delete obj[key].value;
      }
    } else if (key == "operate" || key == "selection" || key == "index") {
      delete obj[key];
    }
  }

  fromlist = {
    index: "2",
    labelwidth: 130,
    formInline: false,
    isInput: true,
    position: "left",
    component: "rigthoptins",
    listData: obj,
    newListData: JSON.parse(JSON.stringify(obj)),
  };
  handleTableActiveClick({props:{name:'0'}})

  proxy.$bus.emit("addEvent", true);
  index == objlength ? (checked4.value = true) : "";
}


function ButtonClick(type: string) {
  let keys = Object.keys(fromlist.listData)
  let list = {
      permission_id:fromlist.listData[keys[0]].permission_id,
      details:[]
  }

  if (type === "save") {
    for (const key in fromlist.listData) {
      let aa ={
        'field_id':fromlist.listData[key].id,
        'status':fromlist.listData[key].status,
        'left_fixed':fromlist.listData[key].left_fixed
      }
      list.details.push(aa)
    }
  proxy.$axios.post('/system/user/list_set_fields', list).then((res) => {
    if(res.code == 200){
      ElMessage.success(res.message)
      proxy.$bus.emit('UpdataTablerowKeys')
    }else if(res.code == 500){
      ElMessage.error(res.message)
    }
  })
  }
}

function beforeClose() {
  Props.DrawerObject.visible = false;
  proxy.$bus.emit("addEvent", false);
}

proxy.$bus.on("UpdataRouterPath", (val: boolean) => {
  Props.DrawerObject.visible = val;
});

function Drawer_switchChange(
  key:string,
  TableList:any
) {
  fromlist.listData[key][TabactiveName.value == '0' ? 'left_fixed'  : 'status'] = TableList[key]
  Object.keys(TableList).length  == Object.keys(fromlist.listData).length  && Object.values(TableList).every(Boolean) ? checked4.value = true : checked4.value = false
}

function handelCheack() {
  let bol = !checked4.value
  From.value.getUpdataRigthOptions(bol);
  for (const key in fromlist.listData) {
    fromlist.listData[key][TabactiveName.value == '0' ? 'left_fixed'  : 'status'] = From.value.TableList[key]
  }
  
}

let inputval = ref("");
function handelInput(value: string) {
  inputval.value = value;
  setTimeout(() => {
    aa(value);
  }, 1000);
}

function aa(val: string) {
  if (inputval.value == val) {
    let list = JSON.parse(JSON.stringify(fromlist.newListData));
    for (const key in list) {
      if (val == "") {
        fromlist.listData = JSON.parse(JSON.stringify(fromlist.newListData));
      }
      if (list[key].title.indexOf(val) == -1) {
        delete list[key];
      }
    }
    fromlist.listData = list;
    formkey.value++;
  }
}

function handleTableActiveClick(value: any) {
  TabactiveName.value = value.props.name;
  for (const key in fromlist.listData) {
    fromlist.listData[key].value = fromlist.listData[key][TabactiveName.value == '0' ? 'left_fixed'  : 'status'] 

  }
  checked4.value = Object.keys(fromlist.listData).every(item => fromlist.listData[item].value == true)
  formkey.value++;
}
</script>

<style lang="less" scoped>
::v-deep el-drawer {
  background: saddlebrown !important;
}
::v-deep .el-icon {
  height: none;
}

::v-deep .drawerBox {
  .el-drawer__header {
    padding: 0px !important;
    margin-bottom: 0px !important;
  }
  .el-drawer__body {
    padding: 0px !important;
  }
  .el-input__wrapper {
    box-shadow: none !important;
  }
  .deawerScanner {
    border: none;
    width: 100%;
    padding: 0 9px;
    border-bottom: 1px solid #e8eaf2;
    height: 40px;
    .input__wrapper {
      box-shadow: none !important;
      border: none !important;
    }
    .deawerScanner_input {
      height: 39px !important;
      border: none !important;
    }
  }
  .elForm {
    max-height: none !important;
  }
  .FromDataDom {
    overflow: hidden;
    overflow-y: auto;
    scrollbar-width: thin; /* 设置滚动条为细条 */
    // scrollbar-color: #888 #f1f1f1; /* 设置滚动条和轨道的颜色 */
  }
  .deawerTop {
    width: 100%;
    height: 87%;
    padding: 0px 0px 0 12px;

    .deawerTop_All {
      height: 40px;
      display: flex;
      align-items: center;
    }
  }
  width: 244px !important;
  .deawerTitle {
    padding: 0px 15px !important;
    color: #5b6070 !important;
    font-size: 14px !important;
    width: 214px;
    height: 40px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid #e8eaf2;
  }

  .botton_but {
    width: 83%;
    height: 84px;
    display: flex;
    justify-items: center;
    align-items: center;
    margin-left: 8%;
    .ButBox {
      display: contents;
      background: salmon;
    }
  }
  .bottonMax_but {
    position: fixed;
    bottom: 0;
  }
}

::v-deep.elForm {
  padding: 0px !important;
}
::v-deep .el-drawer {
  height: 89%;
  top: 11% !important;
  bottom: 0 !important;
}

// ::v-deep .drawerBox {
//   position: absolute !important;
//   top: 100 !important;
//   right: 0 !important;
//   bottom: 0 !important;
//   z-index: 12;
// }

::v-deep .drawerDiv {
  inset: 100% !important;
  position: absolute !important;
  top: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
}
::v-deep .el-switch.is-checked .el-switch__core {
  background: var(--Click_Nav_FontColor) !important;
}
::v-deep .el-button--primary {
  background: var(--Click_Nav_FontColor) !important;
}

::v-deep .el-form-item__label {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

::v-deep .el-tabs__nav-wrap::after {
  height: 1px;
}

::v-deep .el-tabs__nav {
  width: 100%;
  display: flex;
  justify-content: center;
}

::v-deep .switchClass {
  display: flex;
  i {
    display: block;
    height: 32px;
    display: flex;
    justify-content: center;
  }
}
::v-deep .el-tabs__item:hover,
::v-deep .el-checkbox__input.is-checked + .el-checkbox__label {
  color: var(--Click_Nav_FontColor) !important;
}
::v-deep .el-tabs__item.is-active {
  color: var(--Click_Nav_FontColor) !important;
}
::cv-deep .el-tabs__active-bar {
  background-color: var(--Click_Nav_FontColor) !important;
}
</style>

table组件

<template>
  <div class="TableBox" id="TableBox">
    <div class="DateilsTitle" v-if="props.PropTableS.title">
      {{ props.PropTableS.title }}
    </div>
    <div v-if="props.PropTableS.headerRemarks" class="headerRemarks">
      {{ props.PropTableS.headerRemarks }}
    </div>
    <el-table
      v-if="TabKys"
      class="singleTableRef"
      ref="singleTableRef"
      :data="getTables()"
      :key="tablekeyindex"
      
    >
      <template v-for="(child, key, index) in TabKys">
            <!-- 存在显示隐藏标识的字段 控制显示列  evalRowShow-->
              <el-table-column
              v-if="props.PropTableS.keySrequest ? evalRowShow(child.status||child.isshow) : true"></el-table-column>
      </template>
      <!-- 小齿轮在右侧显示 -->
      <template v-if="PropTableS.tables" class="aaaaaa">
        <el-table-column fixed="right" width="20px">
          <template #header>
            <Icons
              @click="handelRightIcon(TabKys)"
              :theme="'outline'"
              :size="36"
              :is="'config'"
            />
          </template>
        </el-table-column>
      </template>
    </el-table>
    <!-- 小齿轮组件 -->
    <Drawer
      v-if="DrawerObject.visible"
      :DrawerObject="DrawerObject"
      :primarytablelist="TabKys"
    ></Drawer>

   
  </div>
</template>

<script setup lang="ts">

onMounted(() => {
   //判断是否包含keySrequest ,否则去自定义传递过来的table数据对象
  if(props.PropTableS.keySrequest){
    tablekey()
  }
});

onActivated(() => {
    //看看列信息是否在路由中有存起来,有值就是代表不是第一次进入该页面
  if(props.PropTableS.keySrequest &&  proxy.$router.currentRoute.value.meta.key){
    TabKys = proxy.$router.currentRoute.value.meta.key
  }
});

//点击右侧设置按钮
function handelRightIcon(params: object) {
  DrawerObject.visible = true;
  DrawerObject.TabKys = JSON.parse(JSON.stringify(TabKys));
}
function evalRowShow(params:any){
  return eval(params);
}

  proxy.$bus.on('UpdataTablerowKeys',()=>{
    tablekey()
  })
function tablekey(){
    proxy.$axios.get('/system/user/list_fields', {
      permission_id:proxy.$router.currentRoute.value.meta.id
    }).then((res) => {
      let keycopy = JSON.parse(JSON.stringify(TabKys))
      let list = {}
      res.data.map(item=>{
        list[item.code] = {
          title :item.name,
          left_fixed:item.left_fixed == 1 ? true : false,
          fixed:item.left_fixed == 1 ? 'left' : false,
          id:item.id,
          sort:item.sort,
          status:item.status == 1 ? true : false,
          isshow:item.isshow,
          width:keycopy[item.code].width,
          type:keycopy[item.code].type,
          permission_id:proxy.$router.currentRoute.value.meta.id
        }
      })
      TabKys = list
       //进入页面将列值数据保存
      proxy.$router.options.routes[1].children.forEach(route=>{
        route.meta.id == proxy.$router.currentRoute.value.meta.id ?  route.meta.key == undefined ?  route.meta.key = TabKys : '' : ''
      })
      tablekeyindex.value++
    });
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401060.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pikachu靶场通关笔记13 XSS关卡09-XSS之href输出

目录 一、href 1、常见取值类型 2、使用示例 3、安全风险 二、源码分析 1、进入靶场 2、代码审计 3、渗透思路 三、渗透实战 1、注入payload1 2、注入payload2 3、注入payload3 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关&#xff09;渗透集合&#xff…

MCP客户端Client开发流程

1. uv工具入门使用指南 1.1 uv入门介绍 MCP开发要求借助uv进行虚拟环境创建和依赖管理。 uv 是一个Python 依赖管理工具&#xff0c;类似于pip 和 conda &#xff0c;但它更快、更高效&#xff0c;并且可以更好地管理 Python 虚拟环境和依赖项。它的核心目标是 替代 pip 、…

学习日记-day21-6.3

完成目标&#xff1a; 目录 知识点&#xff1a; 1.集合_哈希表存储过程说明 2.集合_哈希表源码查看 3.集合_哈希表无索引&哈希表有序无序详解 4.集合_TreeSet和TreeMap 5.集合_Hashtable和Vector&Vector源码分析 6.集合_Properties属性集 7.集合_集合嵌套 8.…

C语言探索之旅:深入理解结构体的奥秘

目录 引言 一、什么是结构体&#xff1f; 二、结构体类型的声明和初始化 1、结构体的声明 2、结构体的初始化 3、结构体的特殊声明 4、结构体的自引用 5、结构体的重命名 三、结构体的内存对齐 1、对齐规则 2、为什么存在内存对齐&#xff1f; 3、修改默认对齐数 三…

经典算法回顾之最小生成树

最小生成树&#xff08;Minimum Spanning Tree&#xff0c;简称MST&#xff09;是图论中的一个重要概念&#xff0c;主要用于解决加权无向图中连接所有顶点且总权重最小的树结构问题。本文对两种经典的算法即Prim算法和Kruskal算法进行回顾&#xff0c;并对后者的正确性给出简单…

Ubuntu下实现nginx反向代理

1. 多个ngx实例安装 脚本已经在deepseek的指导下完成啦&#xff01; deepseek写的脚本支持ubuntu/centos两种系统。 ins_prefix"/usr/local/" makefile_gen() {ngx$1 ngx_log_dir"/var/log/"$ngx"/"ngx_temp_path"/var/temp/"${ngx}…

c++ QicsTable使用实例

效果图&#xff1a; #include <QicsTable.h> #include <QicsDataModelDefault.h> #include <QVBoxLayout> Demo1::Demo1(QWidget *parent) : QWidget(parent) { ui.setupUi(this); const int numRows 10; const int numCols 5; // create th…

在WordPress上添加隐私政策页面

在如今的互联网时代&#xff0c;保护用户隐私已经成为每个网站管理员的责任。隐私政策不仅是法律要求&#xff0c;还能提高用户对网站的信任。本文将介绍两种常用方法&#xff0c;帮助你在WordPress上轻松创建并发布隐私政策页面。这些方法简单易行&#xff0c;符合中国用户的阅…

阿里云ACP云计算备考笔记 (3)——云服务器ECS

目录 第一章 整体概览 第二章 ECS简介 1、产品概念 2、ECS对比本地IDC 3、BGP机房优势 第三章 ECS实例 1、实例规格族 2、实例系列 3、应用场景推荐选型 4、实例状态 5、创建实例 ① 完成基础配置 ② 完成网络和安全组配置 ③ 完成管理配置和高级选项 ④ 确认下单…

从零开始:用Tkinter打造你的第一个Python桌面应用

目录 一、界面搭建&#xff1a;像搭积木一样组合控件 二、菜单系统&#xff1a;给应用装上“控制中枢” 三、事件驱动&#xff1a;让界面“活”起来 四、进阶技巧&#xff1a;打造专业级体验 五、部署发布&#xff1a;让作品触手可及 六、学习路径建议 在Python生态中&am…

Web开发主流前后端框架总结

&#x1f5a5; 一、前端主流框架 前端框架的核心是提升用户界面开发效率&#xff0c;实现高交互性应用。当前三大主流框架各有侧重&#xff1a; React (Meta/Facebook) 核心特点&#xff1a;采用组件化架构与虚拟DOM技术&#xff08;减少真实DOM操作&#xff0c;优化渲染性能&…

GlobalSign、DigiCert、Sectigo三种SSL安全证书有什么区别?

‌GlobalSign、DigiCert和Sectigo是三家知名的SSL证书颁发机构&#xff0c;其产品在安全性、功能、价格和适用场景上存在一定差异。选择SSL证书就像为你的网站挑选最合身的“安全盔甲”&#xff0c;核心是匹配你的实际需求&#xff0c;避免过度配置或防护不足。 一、核心特点对…

力扣面试150题--二叉搜索树中第k小的元素

Day 58 题目描述 思路 直接采取中序遍历&#xff0c;不过我们将k参与到中序遍历中&#xff0c;遍历到第k个元素就结束 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* …

SQL Server Agent 不可用怎么办?

在 SQL Server Management Studio (SSMS) 中&#xff0c;SQL Server Agent 通常位于对象资源管理器&#xff08;Object Explorer&#xff09;的树形结构中&#xff0c;作为 SQL Server 实例的子节点。以下是详细说明和可能的原因&#xff1a; 1. SQL Server Agent 的位置 默认路…

css-塞贝尔曲线

文章目录 1、定义2、使用和解释 1、定义 cubic-bezier() 函数定义了一个贝塞尔曲线(Cubic Bezier)语法&#xff1a;cubic-bezier(x1,y1,x2,y2) 2、使用和解释 x1,y1,x2,y2&#xff0c;表示两个点的坐标P1(x1,y1),P2(x2,y2)将以一条直线放在范围只有 1 的坐标轴中&#xff0c;并…

docker使用proxy拉取镜像

前提条件&#xff0c;宿主机可以访问docker hub 虚拟机上telnet 宿主机7890能正常访问 下面的才是关键&#xff0c;上面部分自己想办法~ 3. 编辑 /etc/docker/daemon.json {"proxies": {"http-proxy": "http://192.168.100.1:7890","ht…

服务端定时器的学习(一)

一、定时器 1、定时器是什么&#xff1f; 定时器不仅存在于硬件领域&#xff0c;在软件层面&#xff08;客户端、网页和服务端&#xff09;也普遍应用&#xff0c;核心功能都是高效管理大量延时任务。不同应用场景下&#xff0c;其实现方式和使用方法有所差异。 2、定时器解…

Modbus转EtherNET IP网关开启节能改造新范式

在现代工业生产和能源管理中&#xff0c;无锡耐特森Modbus转EtherNET IP网关MCN-EN3001发挥着至关重要的作用。通过将传统的串行通信协议Modbus转换为基于以太网的EtherNET IP协议&#xff0c;这种网关设备不仅提高了数据传输的效率&#xff0c;而且为能源管理和控制系统的现代…

C#入门学习笔记 #7(传值/引用/输出/数组/具名/可选参数、扩展方法(this参数))

欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 本篇介绍各种参数,参数本质上属于方法的一部分,所以本篇算是对方法更深度的学习。本章难度较大... 传值参数 …

【DeepSeek】【Dify】:用 Dify 对话流+标题关键词注入,让 RAG 准确率飞跃

1 构建对话流处理数据 初始准备 文章大纲摘要 数据标注和清洗 代码执行 特别注解 2 对话流测试 准备工作 大纲生成 清洗片段 整合分段 3 构建知识库 构建 召回测试 4 实战应用测试 关键词提取 智能总结 测试 1 构建对话流处理数据 初始准备 构建对话变量 用…