jiangping
2024-06-12 bd305e527234bf1af5ed0f23b1c9f940083a23bc
最新版本
已修改6个文件
578 ■■■■■ 文件已修改
admin/src/components/common/ImageCropper.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/RichEditor.vue 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/system/dict/DictDataManagerWindow.vue 68 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/system/dict/OperaDictDataWindow.vue 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/filters/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/meeting/rooms.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/ImageCropper.vue
@@ -6,9 +6,12 @@
          <vue-cropper
              ref="cropper"
              :img="imgSrc"
              :width="options.width"
              :height="options.height"
              :canScale="options.canScale"
              :autoCrop="options.autoCrop"
              :canMove="options.canMove"
              :output-size="options.outputSize"
              :centerBox="options.centerBox"
              :autoCropWidth="options.autoCropWidth"
              :autoCropHeight="options.autoCropHeight"
@@ -17,11 +20,6 @@
        </div>
        <div class="previews">
          <img style="width: 100px;height: 100px;border-radius: 50%;object-fit: cover" :src="cutImgSrc" alt="图片">
          <!--          <el-image-->
          <!--            v-if="cutImgSrc"-->
          <!--            style="width: 100px; height: 100px;vertical-align: middle;border-radius: 50%;"-->
          <!--            :src="cutImgSrc"-->
          <!--            fit="fill"></el-image>-->
        </div>
      </div>
    </div>
@@ -76,8 +74,11 @@
        autoCrop: true,
        canMove: false,
        centerBox: true,
        autoCropWidth: 200,
        autoCropHeight: 200,
        height: 300,
        width: 300,
        outputSize:{width: 300, height: 300},
        autoCropWidth: 300,
        autoCropHeight: 300,
        fixed: true,
        fixedNumber: [1, 1]
      },
admin/src/components/common/RichEditor.vue
@@ -1,162 +1,315 @@
<template>
  <div class="wang_editor">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editor"
      :default-config="toolbarConfig"
      :mode="mode"
    />
    <Editor
      v-model="html"
      style="min-height: 200px"
      :default-config="editorConfig"
      :mode="mode"
      @onCreated="onCreated"
    />
  <div :style="styleEditor">
    <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
    <Editor style="height: 300px; overflow-y: hidden;" v-model="html" :defaultConfig="editorConfig" :mode="mode"
            @onCreated="onCreated" @onChange="onChange" />
  </div>
</template>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
import axios from 'axios'
import { uploadFile } from '@/api'
const uploadConfig = {
  action: uploadFile, // 必填参数 图片上传地址
  methods: 'POST', // 必填参数 图片上传方式
  token: '', // 可选参数 如果需要token验证,假设你的token有存放在sessionStorage
  name: 'file', // 必填参数 文件的参数名
  size: 500, // 可选参数   图片大小,单位为Kb, 1M = 1024Kb
  accept: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' // 可选 可上传的图片格式
}
export default {
  components: {
    Editor,
    Toolbar
  },
import { Loading } from 'element-ui';
export default Vue.extend({
  props: {
    info: {
    richData: { // 父组件传递的数据
      type: String,
      default: ''
    },
    default: {
      type: String,
      default: ''
    },
    placeholder: {
      type: String,
      default: '请输入内容...'
    }
    styleEditor: '',
    readonly: false, // 是否可以输入
  },
  data () {
  name:'RichEditor',
  components: { Editor, Toolbar },
  data() {
    return {
      editor: null,
      toolbarConfig: {},
      editorConfig: {
        placeholder: this.placeholder,
      html: '',
      toolbarConfig: { // 工具栏配置
        toolbarKeys: this.readonly ? ["fullScreen"]: [ // 显示指定的菜单项
          "bold", // 粗体
          "underline", // 下划线
          "italic", // 斜体
          "through", // 删除线
          "code", // 行内代码
          "sub", // 下标
          "sup", // 上标
          "clearStyle", // 清除格式
          "color", // 字体颜色
          "bgColor", // 背景色
          "fontSize", // 字号
          "fontFamily", // 字体
          "indent", // 增加缩进
          "delIndent", // 减少缩进
          "justifyLeft", // 左对齐
          "justifyRight", // 右对齐
          "justifyCenter", // 居中对齐
          "justifyJustify", // 两端对齐
          "lineHeight", // 行高
          // "viewImageLink", // 查看链接
          "divider", // 分割线
          "emotion", // 表情
          "insertLink", // 插入链接
          // "editLink", // 修改链接
          // "unLink", // 取消链接
          // "viewLink", // 查看链接
          "codeBlock", // 代码块
          "blockquote", // 引用
          "headerSelect", // 标题
          // "header1", // 标题1
          // "header2", // 标题2
          // "header3", // 标题3
          // "header4", // 标题4
          // "header5", // 标题5
          // "todo", // 待办
          "redo", // 重做
          "undo", // 撤销
          // "enter", // 回车
          // "bulletedList", // 无序列表
          // "numberedList", // 有序列表
          // "codeSelectLang" // 选择语言
          // 表格功能分组
         /* {
            key: 'table-style', // 必填,要以 group 开头
            title: '表格', // 必填
            // iconSvg: '<svg>....</svg>', // 可选
            menuKeys: [
              "insertTable", // 插入表格
              "deleteTable", // 删除表格
              "insertTableRow", // 插入行
              "deleteTableRow", // 删除行
              "insertTableCol", // 插入列
              "deleteTableCol", // 删除列
              "tableHeader", // 表头
              "tableFullWidth", // 宽度自适应
            ] // 下级菜单 key ,必填
          },*/
          // 上传图片分组
         /* {
            key: 'img-style', // 必填,要以 group 开头
            title: '图片', // 必填
            // iconSvg: '<svg>....</svg>', // 可选
            menuKeys: [
              "uploadImage", // 上传图片
              "insertImage", // 网络图片
              "deleteImage", // 删除图片
              "editImage", // 编辑图片
              "imageWidth30", // 图片宽度相对于编辑器宽度的百分比30
              "imageWidth50", // 图片宽度相对于编辑器宽度的百分比50
              "imageWidth100", // 图片宽度相对于编辑器宽度的百分比100
            ] // 下级菜单 key ,必填
          },*/
          // 视频分组
         /* {
            key: 'video-style', // 必填,要以 group 开头
            title: '视频', // 必填
            // iconSvg: '<svg>....</svg>', // 可选
            menuKeys: [
              "insertVideo", // 插入网络视频
              "uploadVideo", // 上传视频
              "editVideoSize", // 修改视频尺寸
            ] // 下级菜单 key ,必填
          },*/
          "fullScreen", // 全屏
        ],
        excludeKeys: [ // 隐藏指定的菜单项
          // 'headerSelect',
          // 'video-style'
          // 排除菜单组,写菜单组 key 的值即可
        ],
      },
      editorConfig: { // 编辑器配置
        placeholder: '请输入内容...',
        readOnly: this.readonly, // 是否只读,默认false
        autoFocus: false, // 是否自动focus,默认为true
        scroll: true, // 配置编辑器是否支持滚动,默认为 true 。注意,此时不要固定 editor-container 的高度,设置一个 min-height 即可。
        maxLength: 20000, // 最大限制,避免内容过多卡顿
        MENU_CONF: {
          // 图片上传
          uploadImage: {
            html: this.info,
            server: uploadFile,
            server: process.env.VUE_APP_BASE_API + "/common/upload",
            fieldName: 'file',
            // 单个文件的最大体积限制,默认为 2M
            maxFileSize: 5 * 1024 * 1024, // 1M
            maxFileSize: 10 * 1024 * 1024, // 10M
            // 最多可上传几个文件,默认为 100
            maxNumberOfFiles: 10,
            // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['image/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
              token: '',
              otherKey: '',
              folder: 'COURSE_IMG'
            },
            // 将 meta 拼接到 url 参数中,默认 false
            metaWithUrl: false,
            // 自定义增加 http  header
            // headers: { Authorization: "Bearer " + getToken() },
            // 跨域是否传递 cookie ,默认为 false
            withCredentials: true,
            // 超时时间,默认为 10 秒
            timeout: 10 * 1000, //10 秒
            // 上传前
            onBeforeUpload(files) {
              Loading.service({
                lock: true,
                text: '上传中...',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
              });
              return files;
            },
            // 自定义插入图片
            customInsert(res, insertFn) {
              console.log(res);
              // 因为自定义插入导致onSuccess与onFailed回调函数不起作用,自己手动处理
              // 先关闭等待的Message
              Loading.service({
                lock: true,
                text: '上传中...',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
              }).close();
              if (res.code === 200) {
                // Message.success({
                //     message: `${res.data.originalName} 上传成功`
                // });
              } else {
                // Message.error({
                //     message: `${res.data.originalName} 上传失败,请重新尝试`
                // });
              }
              insertFn(res.url, res.originalFilename, res.newFileName);
            },
            // 单个文件上传成功之后
            onSuccess(file, res) {
              console.log(`${file.originalFilename} 上传成功`, res);
            },
            // 单个文件上传失败
            onFailed(file, res) {
              console.log(`${file.originalFilename} 上传失败`, res);
            },
            // 上传进度的回调函数
            onProgress(progress) {
              console.log('progress', progress);
              // progress 是 0-100 的数字
            },
            // 上传错误,或者触发 timeout 超时
            onError(file, err, res) {
              console.log(`${file.originalFilename} 上传出错`, err, res);
            }
          },
          // 视频上传
          uploadVideo: {
            fieldName: 'file',
            server: process.env.VUE_APP_BASE_API + "/common/upload",
            // 单个文件的最大体积限制,默认为 10M
            maxFileSize: 50 * 1024 * 1024, // 50M
            // 最多可上传几个文件,默认为 5
            maxNumberOfFiles: 3,
            // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
            allowedFileTypes: ['video/*'],
            // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
            meta: {
              // token: 'xxx',
              // otherKey: 'yyy'
            },
            // 将 meta 拼接到 url 参数中,默认 false
            metaWithUrl: false,
            // 自定义增加 http  header
            headers: {
              token: localStorage.getItem('token') || ''
              // Accept: 'text/x-json',
              // Authorization: "Bearer " + getToken()
              // otherKey: 'xxx'
            },
            // 跨域是否传递 cookie ,默认为 false
            withCredentials: true,
            // 超时时间,默认为 10 秒
            timeout: 5 * 1000, // 5 秒
            onSuccess (file, res) { // TS 语法
              // onSuccess(file, res) {          // JS 语法
              console.log(`${file.name} 上传成功`, res)
            // 超时时间,默认为 30 秒
            timeout: 1000 * 1000, // 1000 秒,
            // 上传之前触发
            onBeforeUpload(file) {
              return file;
            },
            customUpload (file, insertFn) { // TS 语法
              // file 即选中的文件
              // 自己实现上传,并得到图片 url alt href
              // var form = new FormData()
              // form.append('image', file)
              // form.append('folder', 'COURSE_IMG')
              var formData = new FormData()
              formData.append(file.name, file)
              formData.append('image', file)
              formData.append('folder', 'member')
              // formData.append('type', '')
              var xhr = new XMLHttpRequest()
              xhr.open(uploadConfig.methods, uploadConfig.action, true)
              // 上传数据成功,会触发
              xhr.send(formData)
              xhr.onreadystatechange = () => {
                // 若响应完成且请求成功
                if (xhr.readyState === 4 && xhr.status === 200) {
                  const result = JSON.parse(xhr.responseText)
                  console.log('result', result);
                  insertFn(result.data.url, '', result.data.url)
                }
            // 自定义插入视频
            customInsert(res, insertFn) {
              // 因为自定义插入导致onSuccess与onFailed回调函数不起作用,自己手动处理
              // 先关闭等待的Message
              // Message.closeAll();
              if (res.code === 200) {
                // Message.success({
                //     message: `${res.data.originalName} 上传成功`
                // });
              } else {
                // Message.error({
                //     message: `${res.data.originalName} 上传失败,请重新尝试`
                // });
              }
              insertFn(res.data.link, res.data.link);
            },
            customInsert (res, insertFn) { // TS 语法
              // customInsert(res, insertFn) {                  // JS 语法
              // res 即服务端的返回结果
              console.log(res.data.url)
              // 从 res 中找到 url alt href ,然后插入图片
              insertFn(res.url)
            // 上传进度的回调函数
            onProgress(progress) {
              console.log(progress);
              // onProgress(progress) {       // JS 语法
              // progress 是 0-100 的数字
            },
            // // 单个文件上传成功之后
            // onSuccess(file, res) {
            //   console.log(`${file.name} 上传成功`, res);
            //   this.successMsg(file);
            // },
            // // 单个文件上传失败
            // onFailed(file, res) {
            //   console.log(`${file.name} 上传失败`, res);
            //   this.errorMsg(file);
            // },
            // 上传错误,或者触发 timeout 超时
            onError(file, err, res) {
              console.log(`${file.name} 上传出错`, err, res);
              // Notification.error({
              //     title: '错误',
              //     message: `${file.name} 上传失败,请重新尝试`
              // });
            }
          }
        }
      },
      mode: 'default' // or 'simple'
      mode: 'default', // or 'simple'
    }
  },
  emits: ['input'],
  computed: {
    html: {
      get () {
        return this.info || ''
      },
      set (newValue) {
        this.$emit('input', newValue)
      }
    }
  watch: {
    richData: function (value) {
      this.html = value
    },
    readonly: function (value) {
      this.readonly = value
    },
    styleEditor: function (value) {
      this.styleEditor = value
    },
  },
  mounted () {
    setTimeout(() => {
      this.info = this.default
    }, 1200)
  mounted() {
    // 需要在编辑器创建完毕后在赋值
    this.$nextTick(()=>{
      this.html = this.richData
    })
  },
  beforeDestroy () {
  methods: {
    // 编辑器创建完毕时的回调函数
    onCreated(editor) {
      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
    },
    // 编辑器内容、选区变化时的回调函数
    onChange(editor) {
      this.$emit('getWangedditor', editor.getHtml())
      console.log("onChange", editor.getHtml()); // onChange 时获取编辑器最新内容
    },
  },
  beforeDestroy() {
    // 编辑器销毁时的回调函数。调用 editor.destroy() 即可销毁编辑器
    const editor = this.editor
    if (editor == null) return
    editor.destroy() // 组件销毁时,及时销毁编辑器
  },
  methods: {
    onCreated (editor) {
      this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
      this.$emit('input', '123123')
    },
    test () {
      console.log(this.info)
    }
  }
}
})
</script>
<style lang="scss" scoped>
.wang_editor {
  border: 1px solid;
}
</style>
<style lang="scss">
</style>>
admin/src/components/system/dict/DictDataManagerWindow.vue
@@ -1,9 +1,9 @@
<template>
  <GlobalWindow
    :title="dictName + '数据管理'"
    width="78%"
    :visible.sync="visible"
    :with-footer="false"
      :title="dictName + '数据管理'"
      width="78%"
      :visible.sync="visible"
      :with-footer="false"
  >
    <TableLayout :with-breadcrumb="false">
      <!-- 表格和分页 -->
@@ -13,14 +13,21 @@
          <li><el-button @click="deleteByIdInBatch" icon="el-icon-delete">删除</el-button></li>
        </ul>
        <el-table
          v-loading="isWorking.search"
          :data="tableData.list"
          stripe
          @selection-change="handleSelectionChange"
            v-loading="isWorking.search"
            :data="tableData.list"
            stripe
            @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" width="55"></el-table-column>
          <el-table-column prop="label" label="数据标签" min-width="100px"></el-table-column>
          <el-table-column prop="code" label="数据值" min-width="100px" show-overflow-tooltip></el-table-column>
          <el-table-column prop="code" label="数据值" min-width="100px">
            <template slot-scope="{row}">
              <div v-if="row.code || row.code.length>500" >
                <el-button type="text" @click="showCode(row)" >点击查看</el-button>
              </div>
              <div v-else>{{row.code}}</div>
            </template>
          </el-table-column>
          <el-table-column prop="disabled" label="状态" min-width="100px">
            <template slot-scope="{row}">{{row.disabled | disabledText}}</template>
          </el-table-column>
@@ -33,24 +40,40 @@
          <el-table-column prop="createTime" label="创建时间" min-width="100px"></el-table-column>
          <el-table-column prop="updateTime" label="更新时间" min-width="100px"></el-table-column>
          <el-table-column
            label="操作"
            min-width="120"
            fixed="right"
              label="操作"
              min-width="120"
              fixed="right"
          >
            <template slot-scope="{row}">
              <el-button type="text" @click="$refs.operaDictDataWindow.open('编辑字典数据', dictId, row)" icon="el-icon-edit">编辑</el-button>
              <el-button type="text" @click="$refs.operaDictDataWindow.open('编辑字典数据', searchForm.dictId, row)" icon="el-icon-edit">编辑</el-button>
              <el-button type="text" @click="deleteById(row)" icon="el-icon-delete">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          @size-change="handleSizeChange"
          @current-change="handlePageChange"
          :pagination="tableData.pagination"
            @size-change="handleSizeChange"
            @current-change="handlePageChange"
            :pagination="tableData.pagination"
        ></pagination>
      </template>
      <!-- 新建/修改 -->
      <OperaDictDataWindow ref="operaDictDataWindow" @success="handlePageChange(tableData.pagination.pageIndex)"/>
      <el-dialog
          class="center-title"
          title="字典值"
          width="70%"
          height="70%"
          text="字典值"
          :visible.sync="visible1"
          append-to-body
      >
        <div class="agree-list"  v-html="agreement">
        </div>
        <template  v-slot:footer>
          <el-button @click="visible1=false">返回</el-button>
        </template>
      </el-dialog>
    </TableLayout>
  </GlobalWindow>
</template>
@@ -68,6 +91,8 @@
  data () {
    return {
      visible: false,
      visible1: false,
      agreement: '',
      searchForm: {
        // 字典ID
        dictId: null
@@ -83,6 +108,10 @@
      this.dictName = dictName
      this.visible = true
      this.search()
    },
    showCode(row){
      this.agreement=row.code
      this.visible1=true
    }
  },
  created () {
@@ -95,6 +124,12 @@
</script>
<style scoped lang="scss">
.agree-list{
  height: 550px;
  //max-height: 50%;
  overflow: auto;
}
/deep/ .window__body {
  .table-content {
    padding: 0;
@@ -102,5 +137,6 @@
      padding-top: 0;
    }
  }
}
</style>
admin/src/components/system/dict/OperaDictDataWindow.vue
@@ -1,21 +1,21 @@
<template>
  <GlobalWindow
    :title="title"
    :visible.sync="visible"
    :confirm-working="isWorking.create"
    @confirm="confirm"
      :title="title"
      :visible.sync="visible"
      :confirm-working="isWorking.create"
      @confirm="confirm"
  >
    <el-form :model="form" ref="form" :rules="rules">
      <el-form-item label="数据标签" prop="label" required>
        <el-input v-model="form.label" placeholder="请输入数据标签" v-trim maxlength="50"/>
      </el-form-item>
      <el-form-item label="是否富文本" prop="istext" >
        <el-switch v-model="form.istext" :active-value="true" :inactive-value="false"/>
        <span class="status-text">{{form.istext | disabledText1}}</span>
      </el-form-item>
      <el-form-item label="数据值" prop="code" required>
        <el-tiptap
                v-if="[175].includes(form.id)"
                v-model="form.code"
                :extensions="extensions"
        />
        <el-input v-else v-model="form.code" placeholder="请输入数据值" v-trim />
        <el-input v-if="!form.istext" v-model="form.code" placeholder="请输入数据值" v-trim maxlength="50"/>
        <RichEditor v-else  :richData="form.code" :styleEditor="styleEditor" @getWangedditor="getWangedditor" :readonly="false"/>
      </el-form-item>
      <el-form-item label="状态" prop="disabled" required class="form-item-status">
        <el-switch v-model="form.disabled" :active-value="false" :inactive-value="true"/>
@@ -28,44 +28,22 @@
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
import { upload } from '@/api/system/common'
import {
  // necessary extensions
  Doc,
  Text,
  Paragraph,
  Heading,
  Bold,
  Underline,
  Italic,
  Strike,
  ListItem,
  BulletList,
  OrderedList,
  Image,
  Blockquote,
  TextAlign,
  Indent,
  Table,
  TableHeader,
  TableCell,
  TableRow,
  TextColor,
  HorizontalRule
} from 'element-tiptap'
import RichEditor from '@/components/common/RichEditor'
export default {
  name: 'OperaDictDataWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  components: { GlobalWindow,RichEditor },
  data () {
    return {
      // 表单数据
      styleEditor:'border: 1px solid #ccc;display: inline-block;',
      form: {
        id: null,
        dictId: null,
        code: '',
        label: '',
        disabled: false
        disabled: false,
        istext: false
      },
      // 验证规则
      rules: {
@@ -75,42 +53,13 @@
        code: [
          { required: true, message: '请输入数据值' }
        ]
      },
      extensions: [
        new Doc(),
        new Text(),
        new Paragraph(),
        new Heading({ level: 5 }),
        new Bold({ bubble: true }),
        new Underline({ bubble: true, menubar: false }),
        new Italic(),
        new Strike(),
        new ListItem(),
        new BulletList(),
        new OrderedList(),
        new Image({
          uploadRequest (file) {
            const fd = new FormData()
            fd.append('folder', 'visit')
            fd.append('file', file)
            return upload(fd).then(res => {
              return res.url
            })
          }
        }),
        new Blockquote(),
        new TextAlign(),
        new Indent(),
        new Table(),
        new TableHeader(),
        new TableCell(),
        new TableRow(),
        new HorizontalRule(),
        new TextColor()
      ]
      }
    }
  },
  methods: {
    getWangedditor(val){
      this.form.code =val
    },
    /**
     * @title 窗口标题
     * @dict 所属字典ID
@@ -125,6 +74,7 @@
          this.$refs.form.resetFields()
          this.form.id = null
          this.form.dictId = dictId
          this.form.istext=false
        })
        return
      }
@@ -132,6 +82,8 @@
      this.$nextTick(() => {
        for (const key in this.form) {
          this.form[key] = target[key]
          this.form.dictId = dictId
          this.form.istext=false
        }
      })
    }
admin/src/filters/index.js
@@ -17,5 +17,11 @@
      }
      return '启用'
    })
    Vue.filter('disabledText1', (value) => {
      if (value) {
        return '普通文本'
      }
      return '富文本'
    })
  }
}
admin/src/views/meeting/rooms.vue
@@ -68,7 +68,7 @@
        </el-table-column>
        <el-table-column prop="limitNum" label="可选服务项" align="center" min-width="140px" show-overflow-tooltip>
          <template slot-scope="{row}">
            <div class="long-title-style">{{ row.projectList.map(item => item.projectName).join('|') }}</div>
            <div class="long-title-style">{{ row.projectList.map(item => item.projectName).join(' | ') }}</div>
          </template>
        </el-table-column>