<!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>