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">內(nèi)容:{{ file.content }}</div>
</div>
</div>
<div class="hint">
說明:勾選文件會(huì)進(jìn)入右側(cè)表格。<br />
每個(gè)文件可選擇多個(gè)印章;每個(gè)印章會(huì)在表格中占一行。<br />
只有某個(gè)文件在右側(cè)所有行都刪除后,左側(cè)才會(huì)取消勾選。
</div>
</div>
</el-col>

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

        <el-table
          :data="tableRows"
          :row-key="getRowKey"
          border
          style="width: 100%"
          empty-text="暫無數(shù)據(jù),先在左側(cè)勾選文件"
        >
          <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="文件內(nèi)容" 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>
  // 創(chuàng)建 Vue 根實(shí)例,掛載整個(gè)頁面交互邏輯
  new Vue({
    // 指定掛載點(diǎn)
    el: "#app",
    // 組件狀態(tài)(響應(yīng)式數(shù)據(jù))
    data: function () {
      // 返回 data 對(duì)象
      return {
        // 左側(cè)文件源數(shù)據(jù)
        files: [
          // 文件1
          { id: 1, name: "文件1", content: "這是文件1的內(nèi)容" },
          // 文件2
          { id: 2, name: "文件2", content: "這是文件2的內(nèi)容" },
          // 文件3
          { id: 3, name: "文件3", content: "這是文件3的內(nèi)容" },
          // 文件4
          { id: 4, name: "文件4", content: "這是文件4的內(nèi)容" }
        ],
        // 可選印章數(shù)據(jù)源
        stampOptions: [
          // 印章 A
          { id: "A", name: "合同章" },
          // 印章 B
          { id: "B", name: "財(cái)務(wù)章" },
          // 印章 C
          { id: "C", name: "人事章" },
          // 印章 D
          { id: "D", name: "公章" }
        ],
        // 左側(cè)當(dāng)前勾選的文件 ID 列表
        selectedFileIds: [],
        // 文件 -> 印章列表映射,如:{ 1: ["A","C"] }
        selectedStampsMap: {},
        // 印章彈窗是否可見
        stampDialogVisible: false,
        // 當(dāng)前正在編輯的文件 ID
        editingFileId: null,
        // 彈窗中的臨時(shí)選擇數(shù)組(點(diǎn)確定才提交)
        currentDraftStamps: []
      };
    },
    // 派生數(shù)據(jù)
    computed: {
      // 右側(cè)表格數(shù)據(jù):按文件與印章關(guān)系展開成多行
      tableRows: function () {
        // 最終返回給表格的數(shù)據(jù)行
        var rows = [];
        // 保存 this 引用給回調(diào)使用
        var _this = this;
        // 遍歷所有已勾選文件
        this.selectedFileIds.forEach(function (fileId) {
          // 按 fileId 找到文件詳情
          var file = _this.files.find(function (f) {
            // 匹配當(dāng)前 fileId
            return f.id === fileId;
          });
          // 找不到文件則跳過
          if (!file) return;
          // 讀取該文件已選印章列表
          var stamps = _this.selectedStampsMap[fileId] || [];
          // 沒有印章時(shí)展示占位行
          if (!stamps.length) {
            // 推入占位行
            rows.push({
              // 占位行唯一鍵
              rowKey: fileId + "-empty",
              // 文件 ID
              fileId: file.id,
              // 文件名稱
              fileName: file.name,
              // 文件內(nèi)容
              content: file.content,
              // 無印章
              stampId: null
            });
            // 當(dāng)前文件處理結(jié)束
            return;
          }
          // 有印章時(shí),一個(gè)印章生成一行
          stamps.forEach(function (stampId) {
            // 推入印章行
            rows.push({
              // 行唯一鍵
              rowKey: fileId + "-" + stampId,
              // 文件 ID
              fileId: file.id,
              // 文件名稱
              fileName: file.name,
              // 文件內(nèi)容
              content: file.content,
              // 當(dāng)前行印章 ID
              stampId: stampId
            });
          });
        });
        // 返回最終表格行
        return rows;
      }
    },
    // 事件與業(yè)務(wù)方法
    methods: {
      // 返回表格行唯一 key
      getRowKey: function (row) {
        // 直接返回 rowKey
        return row.rowKey;
      },
      // 打開印章彈窗并加載當(dāng)前文件草稿
      openStampDialog: function (fileId) {
        // 取當(dāng)前文件已選印章(不存在則空數(shù)組)
        var current = this.selectedStampsMap[fileId] || [];
        // 記錄當(dāng)前編輯文件
        this.editingFileId = fileId;
        // 復(fù)制一份數(shù)組作為草稿,避免直接改已提交數(shù)據(jù)
        this.currentDraftStamps = current.slice();
        // 打開彈窗
        this.stampDialogVisible = true;
      },
      // 關(guān)閉印章彈窗
      closeStampDialog: function () {
        // 僅修改可見狀態(tài)
        this.stampDialogVisible = false;
      },
      // 彈窗關(guān)閉后的清理動(dòng)作
      onStampDialogClose: function () {
        // 清空草稿
        this.currentDraftStamps = [];
        // 清空正在編輯的文件標(biāo)記
        this.editingFileId = null;
      },
      // 點(diǎn)擊“確定”提交彈窗草稿
      confirmStampDialog: function () {
        // 沒有編輯目標(biāo)時(shí)直接關(guān)閉并退出
        if (this.editingFileId === null || this.editingFileId === undefined) {
          // 關(guān)閉彈窗
          this.closeStampDialog();
          // 結(jié)束方法
          return;
        }
        // 用 $set 確保對(duì)象屬性更新可響應(yīng)
        this.$set(
          // 目標(biāo)對(duì)象
          this.selectedStampsMap,
          // 目標(biāo)鍵(文件ID)
          this.editingFileId,
          // 提交草稿副本
          this.currentDraftStamps.slice()
        );
        // 關(guān)閉彈窗
        this.closeStampDialog();
      },
      // 根據(jù)印章 ID 獲取印章名稱
      stampNameById: function (stampId) {
        // 在印章列表中查找對(duì)應(yīng)項(xiàng)
        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;
      },
      // 左側(cè)勾選/取消勾選邏輯
      onFileCheckChange: function (file, checked) {
        // 先判斷當(dāng)前是否已存在
        var exists = this.isFileChecked(file.id);
        // 新勾選且原先不存在
        if (checked && !exists) {
          // 加入選中文件列表
          this.selectedFileIds.push(file.id);
          // 初始化該文件印章數(shù)組
          if (!this.selectedStampsMap[file.id]) {
            // 使用 $set 保證響應(yīng)式
            this.$set(this.selectedStampsMap, file.id, []);
          }
        }
        // 取消勾選且原先存在
        if (!checked && exists) {
          // 從選中文件列表中移除該文件
          this.selectedFileIds = this.selectedFileIds.filter(function (id) {
            // 保留不是當(dāng)前文件的項(xiàng)
            return id !== file.id;
          });
          // 刪除該文件的印章映射
          this.$delete(this.selectedStampsMap, file.id);
          // 若當(dāng)前彈窗正在編輯該文件,則關(guān)閉彈窗
          if (this.editingFileId === file.id) {
            // 關(guān)閉彈窗
            this.closeStampDialog();
          }
        }
      },
      // 刪除右側(cè)某行數(shù)據(jù)
      removeRow: function (row) {
        // 當(dāng)前行對(duì)應(yīng)文件 ID
        var fileId = row.fileId;
        // 取當(dāng)前文件所有印章
        var stamps = this.selectedStampsMap[fileId] || [];
        // 分支1:刪除的是具體印章行
        if (row.stampId) {
          // 過濾掉當(dāng)前行印章,得到刪除后的數(shù)組
          var nextStamps = stamps.filter(function (id) {
            // 保留不是當(dāng)前印章的項(xiàng)
            return id !== row.stampId;
          });
          // 刪除后沒有印章了
          if (!nextStamps.length) {
            // 從左側(cè)選中文件列表移除該文件
            this.selectedFileIds = this.selectedFileIds.filter(function (id) {
              // 保留不是當(dāng)前文件的項(xiàng)
              return id !== fileId;
            });
            // 刪除該文件的印章映射
            this.$delete(this.selectedStampsMap, fileId);
            // 如果彈窗正在編輯該文件,順便關(guān)閉
            if (this.editingFileId === fileId) {
              // 關(guān)閉彈窗
              this.closeStampDialog();
            }
          } else {
            // 仍有印章時(shí)只更新印章數(shù)組
            this.$set(this.selectedStampsMap, fileId, nextStamps);
          }
        } else {
          // 分支2:刪除的是“未選擇印章”的占位行
          this.selectedFileIds = this.selectedFileIds.filter(function (id) {
            // 保留不是當(dāng)前文件的項(xiàng)
            return id !== fileId;
          });
          // 刪除該文件印章映射
          this.$delete(this.selectedStampsMap, fileId);
          // 如果彈窗正在編輯該文件,順便關(guān)閉
          if (this.editingFileId === fileId) {
            // 關(guān)閉彈窗
            this.closeStampDialog();
          }
        }
      }
    }
  });
</script>

</body>
</html>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容