2026-04-08

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue + ElementUI 文件与印章 Demo</title>
<link
rel="stylesheet"
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
/>
<style>
body {
margin: 0;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif;
background: #f5f7fa;
}

  .page {
    max-width: 1200px;
    margin: 0 auto;
  }

  .card {
    background: #fff;
    border-radius: 8px;
    padding: 16px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
    min-height: 560px;
  }

  .title {
    margin: 0 0 14px;
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }

  .left-list {
    border: 1px solid #ebeef5;
    border-radius: 6px;
    max-height: 500px;
    overflow: auto;
  }

  .left-item {
    padding: 10px 12px;
    border-bottom: 1px solid #f2f6fc;
  }

  .left-item:last-child {
    border-bottom: none;
  }

  .file-content {
    margin-top: 4px;
    color: #909399;
    font-size: 12px;
    line-height: 1.5;
  }

  .stamp-tag {
    margin-right: 6px;
    margin-bottom: 6px;
  }

  .hint {
    margin-top: 12px;
    color: #909399;
    font-size: 12px;
    line-height: 1.6;
  }
</style>

</head>

<body>
<div id="app" class="page">
<el-row :gutter="16">

<el-col :span="8">
<div class="card">
<h3 class="title">文件列表</h3>
<div class="left-list">
<div class="left-item" v-for="file in files" :key="file.id">
<el-checkbox
:value="isFileChecked(file.id)"
@change="onFileCheckChange(file, $event)"
>
{{ file.name }}(ID: {{ file.id }})
</el-checkbox>
<div class="file-content">内容:{{ file.content }}</div>
</div>
</div>
<div class="hint">
说明:勾选文件会进入右侧表格。<br />
每个文件可选择多个印章;每个印章会在表格中占一行。<br />
只有某个文件在右侧所有行都删除后,左侧才会取消勾选。
</div>
</div>
</el-col>

    <!-- 右侧:选中文件表格 -->
    <el-col :span="16">
      <div class="card">
        <h3 class="title">已选文件(按印章拆行展示)</h3>

        <el-table
          :data="tableRows"
          :row-key="getRowKey"
          border
          style="width: 100%"
          empty-text="暂无数据,先在左侧勾选文件"
        >
          <el-table-column label="文件ID" width="100">
            <template slot-scope="scope">{{ scope.row.fileId }}</template>
          </el-table-column>
          <el-table-column label="文件名称" width="180">
            <template slot-scope="scope">{{ scope.row.fileName }}</template>
          </el-table-column>
          <el-table-column label="选择的印章" min-width="280">
            <template slot-scope="scope">
              <el-tag
                v-if="scope.row.stampId"
                size="small"
                type="success"
                class="stamp-tag"
              >
                {{ stampNameById(scope.row.stampId) }}
              </el-tag>
              <span v-else style="color: #c0c4cc">未选择印章</span>

              <el-button
                size="mini"
                type="primary"
                plain
                @click="openStampDialog(scope.row.fileId)"
              >
                选择印章
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="文件内容" min-width="240">
            <template slot-scope="scope">{{ scope.row.content }}</template>
          </el-table-column>

          <el-table-column label="操作" width="120" fixed="right">
            <template slot-scope="scope">
              <el-button
                size="mini"
                type="danger"
                @click="removeRow(scope.row)"
              >
                删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>

        <el-dialog
          title="选择印章"
          :visible.sync="stampDialogVisible"
          width="360px"
          :close-on-click-modal="false"
          @close="onStampDialogClose"
        >
          <el-checkbox-group v-model="currentDraftStamps">
            <el-checkbox
              v-for="stamp in stampOptions"
              :key="stamp.id"
              :label="stamp.id"
              style="display: block; margin: 6px 0"
            >
              {{ stamp.name }}
            </el-checkbox>
          </el-checkbox-group>
          <span slot="footer" class="dialog-footer">
            <el-button @click="closeStampDialog">取消</el-button>
            <el-button type="primary" @click="confirmStampDialog">确定</el-button>
          </span>
        </el-dialog>
      </div>
    </el-col>
  </el-row>
</div>

<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
  // 创建 Vue 根实例,挂载整个页面交互逻辑
  new Vue({
    // 指定挂载点
    el: "#app",
    // 组件状态(响应式数据)
    data: function () {
      // 返回 data 对象
      return {
        // 左侧文件源数据
        files: [
          // 文件1
          { id: 1, name: "文件1", content: "这是文件1的内容" },
          // 文件2
          { id: 2, name: "文件2", content: "这是文件2的内容" },
          // 文件3
          { id: 3, name: "文件3", content: "这是文件3的内容" },
          // 文件4
          { id: 4, name: "文件4", content: "这是文件4的内容" }
        ],
        // 可选印章数据源
        stampOptions: [
          // 印章 A
          { id: "A", name: "合同章" },
          // 印章 B
          { id: "B", name: "财务章" },
          // 印章 C
          { id: "C", name: "人事章" },
          // 印章 D
          { id: "D", name: "公章" }
        ],
        // 左侧当前勾选的文件 ID 列表
        selectedFileIds: [],
        // 文件 -> 印章列表映射,如:{ 1: ["A","C"] }
        selectedStampsMap: {},
        // 印章弹窗是否可见
        stampDialogVisible: false,
        // 当前正在编辑的文件 ID
        editingFileId: null,
        // 弹窗中的临时选择数组(点确定才提交)
        currentDraftStamps: []
      };
    },
    // 派生数据
    computed: {
      // 右侧表格数据:按文件与印章关系展开成多行
      tableRows: function () {
        // 最终返回给表格的数据行
        var rows = [];
        // 保存 this 引用给回调使用
        var _this = this;
        // 遍历所有已勾选文件
        this.selectedFileIds.forEach(function (fileId) {
          // 按 fileId 找到文件详情
          var file = _this.files.find(function (f) {
            // 匹配当前 fileId
            return f.id === fileId;
          });
          // 找不到文件则跳过
          if (!file) return;
          // 读取该文件已选印章列表
          var stamps = _this.selectedStampsMap[fileId] || [];
          // 没有印章时展示占位行
          if (!stamps.length) {
            // 推入占位行
            rows.push({
              // 占位行唯一键
              rowKey: fileId + "-empty",
              // 文件 ID
              fileId: file.id,
              // 文件名称
              fileName: file.name,
              // 文件内容
              content: file.content,
              // 无印章
              stampId: null
            });
            // 当前文件处理结束
            return;
          }
          // 有印章时,一个印章生成一行
          stamps.forEach(function (stampId) {
            // 推入印章行
            rows.push({
              // 行唯一键
              rowKey: fileId + "-" + stampId,
              // 文件 ID
              fileId: file.id,
              // 文件名称
              fileName: file.name,
              // 文件内容
              content: file.content,
              // 当前行印章 ID
              stampId: stampId
            });
          });
        });
        // 返回最终表格行
        return rows;
      }
    },
    // 事件与业务方法
    methods: {
      // 返回表格行唯一 key
      getRowKey: function (row) {
        // 直接返回 rowKey
        return row.rowKey;
      },
      // 打开印章弹窗并加载当前文件草稿
      openStampDialog: function (fileId) {
        // 取当前文件已选印章(不存在则空数组)
        var current = this.selectedStampsMap[fileId] || [];
        // 记录当前编辑文件
        this.editingFileId = fileId;
        // 复制一份数组作为草稿,避免直接改已提交数据
        this.currentDraftStamps = current.slice();
        // 打开弹窗
        this.stampDialogVisible = true;
      },
      // 关闭印章弹窗
      closeStampDialog: function () {
        // 仅修改可见状态
        this.stampDialogVisible = false;
      },
      // 弹窗关闭后的清理动作
      onStampDialogClose: function () {
        // 清空草稿
        this.currentDraftStamps = [];
        // 清空正在编辑的文件标记
        this.editingFileId = null;
      },
      // 点击“确定”提交弹窗草稿
      confirmStampDialog: function () {
        // 没有编辑目标时直接关闭并退出
        if (this.editingFileId === null || this.editingFileId === undefined) {
          // 关闭弹窗
          this.closeStampDialog();
          // 结束方法
          return;
        }
        // 用 $set 确保对象属性更新可响应
        this.$set(
          // 目标对象
          this.selectedStampsMap,
          // 目标键(文件ID)
          this.editingFileId,
          // 提交草稿副本
          this.currentDraftStamps.slice()
        );
        // 关闭弹窗
        this.closeStampDialog();
      },
      // 根据印章 ID 获取印章名称
      stampNameById: function (stampId) {
        // 在印章列表中查找对应项
        var stamp = this.stampOptions.find(function (s) {
          // 比较 id
          return s.id === stampId;
        });
        // 找到返回名称,找不到返回空字符串
        return stamp ? stamp.name : "";
      },
      // 判断某文件是否已勾选
      isFileChecked: function (fileId) {
        // indexOf 大于 -1 代表存在
        return this.selectedFileIds.indexOf(fileId) > -1;
      },
      // 左侧勾选/取消勾选逻辑
      onFileCheckChange: function (file, checked) {
        // 先判断当前是否已存在
        var exists = this.isFileChecked(file.id);
        // 新勾选且原先不存在
        if (checked && !exists) {
          // 加入选中文件列表
          this.selectedFileIds.push(file.id);
          // 初始化该文件印章数组
          if (!this.selectedStampsMap[file.id]) {
            // 使用 $set 保证响应式
            this.$set(this.selectedStampsMap, file.id, []);
          }
        }
        // 取消勾选且原先存在
        if (!checked && exists) {
          // 从选中文件列表中移除该文件
          this.selectedFileIds = this.selectedFileIds.filter(function (id) {
            // 保留不是当前文件的项
            return id !== file.id;
          });
          // 删除该文件的印章映射
          this.$delete(this.selectedStampsMap, file.id);
          // 若当前弹窗正在编辑该文件,则关闭弹窗
          if (this.editingFileId === file.id) {
            // 关闭弹窗
            this.closeStampDialog();
          }
        }
      },
      // 删除右侧某行数据
      removeRow: function (row) {
        // 当前行对应文件 ID
        var fileId = row.fileId;
        // 取当前文件所有印章
        var stamps = this.selectedStampsMap[fileId] || [];
        // 分支1:删除的是具体印章行
        if (row.stampId) {
          // 过滤掉当前行印章,得到删除后的数组
          var nextStamps = stamps.filter(function (id) {
            // 保留不是当前印章的项
            return id !== row.stampId;
          });
          // 删除后没有印章了
          if (!nextStamps.length) {
            // 从左侧选中文件列表移除该文件
            this.selectedFileIds = this.selectedFileIds.filter(function (id) {
              // 保留不是当前文件的项
              return id !== fileId;
            });
            // 删除该文件的印章映射
            this.$delete(this.selectedStampsMap, fileId);
            // 如果弹窗正在编辑该文件,顺便关闭
            if (this.editingFileId === fileId) {
              // 关闭弹窗
              this.closeStampDialog();
            }
          } else {
            // 仍有印章时只更新印章数组
            this.$set(this.selectedStampsMap, fileId, nextStamps);
          }
        } else {
          // 分支2:删除的是“未选择印章”的占位行
          this.selectedFileIds = this.selectedFileIds.filter(function (id) {
            // 保留不是当前文件的项
            return id !== fileId;
          });
          // 删除该文件印章映射
          this.$delete(this.selectedStampsMap, fileId);
          // 如果弹窗正在编辑该文件,顺便关闭
          if (this.editingFileId === fileId) {
            // 关闭弹窗
            this.closeStampDialog();
          }
        }
      }
    }
  });
</script>

</body>
</html>

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容