Files
ai-agent-admin/web/apps/web-ele/src/components/contract-design/components/MaterialPanel.vue
T
2026-06-08 18:14:59 +08:00

972 lines
30 KiB
Vue

<script setup lang="ts">
import type {
ContractMaterial,
ContractTemplateConfig,
} from '../store/contractDesignStore';
import { computed, ref } from 'vue';
import {
AlignLeft,
Calendar,
Circle,
Code,
FileText,
Heading1,
Image,
List,
Minus,
Pencil,
Search,
Table,
} from '@vben/icons';
import { $t } from '@vben/locales';
import {
ElIcon,
ElInput,
ElMessage,
ElScrollbar,
ElTabPane,
ElTabs,
} from 'element-plus';
import draggable from 'vuedraggable';
import {
contractMaterials,
useContractDesignStore,
} from '../store/contractDesignStore';
const store = useContractDesignStore();
// 当前 Tab
const activeTab = ref('elements');
const searchKeyword = ref('');
const activeGroups = ref(['text', 'variable', 'signature', 'layout']);
// 图标映射
const iconMap: Record<string, any> = {
Heading1,
AlignLeft,
FileText,
Code,
Table,
Pencil,
Circle,
Calendar,
Minus,
Image,
};
const getIcon = (iconName: string) => {
return iconMap[iconName] || FileText;
};
// 分组材料
const groupedMaterials = computed(() => {
const groups: Record<string, ContractMaterial[]> = {
text: [],
variable: [],
signature: [],
layout: [],
};
contractMaterials.forEach((material) => {
if (groups[material.category]) {
groups[material.category].push(material);
}
});
return groups;
});
// 过滤后的材料
const filteredMaterials = computed(() => {
if (!searchKeyword.value) return groupedMaterials.value;
const keyword = searchKeyword.value.toLowerCase();
const result: Record<string, ContractMaterial[]> = {
text: [],
variable: [],
signature: [],
layout: [],
};
Object.entries(groupedMaterials.value).forEach(([category, materials]) => {
result[category] = materials.filter(
(m) =>
m.title.toLowerCase().includes(keyword) ||
m.type.toLowerCase().includes(keyword),
);
});
return result;
});
const getGroupLabel = (group: string) => {
const labels: Record<string, string> = {
text: $t('contract-design.textElements'),
variable: $t('contract-design.variableElements'),
signature: $t('contract-design.signatureElements'),
layout: $t('contract-design.layoutElements'),
};
return labels[group] || group;
};
const toggleGroup = (group: string) => {
const index = activeGroups.value.indexOf(group);
if (index === -1) {
activeGroups.value.push(group);
} else {
activeGroups.value.splice(index, 1);
}
};
const onDragStart = () => store.setDragging(true);
const onDragEnd = () => store.setDragging(false);
// 点击添加元素
const handleClickAdd = (material: ContractMaterial) => {
store.addElement(material);
};
// 克隆函数
const cloneMaterial = (material: ContractMaterial) => {
return store.cloneElement(material);
};
// 示例模板列表
interface TemplateItem {
id: string;
name: string;
description: string;
category: string;
config: ContractTemplateConfig;
}
const getTemplateCategoryLabel = (key: string) => {
const labels: Record<string, string> = {
common: $t('contract-design.commonContracts'),
hr: $t('contract-design.hrContracts'),
business: $t('contract-design.businessContracts'),
};
return labels[key] || key;
};
// 获取元素标题的国际化文本
const getMaterialTitle = (type: string): string => {
const titleMap: Record<string, string> = {
title: $t('contract-design.contractTitle'),
paragraph: $t('contract-design.paragraphText'),
'rich-text': $t('contract-design.richText'),
variable: $t('contract-design.variablePlaceholder'),
table: $t('contract-design.table'),
'signature-zone': $t('contract-design.signatureZone'),
'seal-zone': $t('contract-design.sealZone'),
'date-zone': $t('contract-design.dateZone'),
divider: $t('contract-design.divider'),
'page-break': $t('contract-design.pageBreak'),
image: $t('contract-design.image'),
};
return titleMap[type] || type;
};
const templateCategories = computed(() => [
{ key: 'common', label: getTemplateCategoryLabel('common') },
{ key: 'hr', label: getTemplateCategoryLabel('hr') },
{ key: 'business', label: getTemplateCategoryLabel('business') },
]);
const sampleTemplates: TemplateItem[] = [
{
id: 'purchase',
name: '采购合同',
description: '标准采购合同模板',
category: 'common',
config: {
id: 'purchase-template',
name: '采购合同',
code: 'PURCHASE_CONTRACT',
category: '常用合同',
description: '标准采购合同模板',
pageSize: 'A4',
pageMargin: { top: 60, right: 60, bottom: 60, left: 60 },
elements: [
{
id: 'title-1',
type: 'title',
props: {
content: '采购合同',
level: 1,
align: 'center',
fontSize: 24,
fontWeight: 'bold',
},
},
{
id: 'rich-1',
type: 'rich-text',
props: {
content: `<p style="text-align: right;">合同编号:{{contract_no}}</p>
<p><br></p>
<p><strong>甲方(采购方):</strong>{{party_a_name}}</p>
<p><strong>地址:</strong>{{party_a_address}}</p>
<p><strong>联系电话:</strong>{{party_a_phone}}</p>
<p><br></p>
<p><strong>乙方(供应方):</strong>{{party_b_name}}</p>
<p><strong>地址:</strong>{{party_b_address}}</p>
<p><strong>联系电话:</strong>{{party_b_phone}}</p>
<p><br></p>
<p>根据《中华人民共和国民法典》及相关法律法规,甲乙双方本着平等互利、诚实信用的原则,经友好协商,就甲方向乙方采购货物事宜达成如下协议:</p>
<p><br></p>
<h3>第一条 采购货物</h3>
<p>甲方向乙方采购货物,详见附件清单。</p>
<p><br></p>
<h3>第二条 合同金额</h3>
<p>本合同总金额为人民币{{contract_amount}}元。</p>
<p><br></p>
<h3>第三条 交货时间及地点</h3>
<p>1. 交货时间:{{start_date}}</p>
<p>2. 交货地点:{{sign_location}}</p>
<p><br></p>
<h3>第四条 付款方式</h3>
<p>合同签订后{{payment_days}}日内支付全款。</p>
<p><br></p>
<h3>第五条 验收标准</h3>
<p>货物到达后,甲方应在7日内完成验收。如有质量问题,应在验收期内书面通知乙方。</p>
<p><br></p>
<h3>第六条 违约责任</h3>
<p>任何一方违反本合同约定,应承担违约责任,赔偿对方因此遭受的损失。</p>
<p><br></p>
<h3>第七条 争议解决</h3>
<p>本合同在履行过程中发生争议,双方应协商解决;协商不成的,可向甲方所在地人民法院提起诉讼。</p>
<p><br></p>
<p>本合同一式两份,甲乙双方各执一份,自双方签字盖章之日起生效。</p>
<p><br></p>
<p>签订日期:{{sign_date}}</p>`,
minHeight: 100,
padding: 8,
showBorder: false,
},
},
{
id: 'sig-a',
type: 'signature-zone',
props: { width: 200, height: 80, label: '甲方签章' },
signature: {
partyType: 'party_a',
partyLabel: '甲方',
showDate: true,
showSeal: false,
required: true,
},
},
{
id: 'sig-b',
type: 'signature-zone',
props: { width: 200, height: 80, label: '乙方签章' },
signature: {
partyType: 'party_b',
partyLabel: '乙方',
showDate: true,
showSeal: false,
required: true,
},
},
],
variables: [
{
code: 'payment_days',
name: '付款天数',
type: 'number',
required: true,
},
],
parties: [
{ type: 'party_a', label: '甲方(采购方)', required: true },
{ type: 'party_b', label: '乙方(供应方)', required: true },
],
},
},
{
id: 'labor',
name: '劳动合同',
description: '标准劳动合同模板',
category: 'hr',
config: {
id: 'labor-template',
name: '劳动合同',
code: 'LABOR_CONTRACT',
category: '人事合同',
description: '标准劳动合同模板',
pageSize: 'A4',
pageMargin: { top: 60, right: 60, bottom: 60, left: 60 },
elements: [
{
id: 'title-1',
type: 'title',
props: {
content: '劳动合同书',
level: 1,
align: 'center',
fontSize: 26,
fontWeight: 'bold',
},
},
{
id: 'rich-1',
type: 'rich-text',
props: {
content: `<p style="text-align: center;"><br></p>
<p><strong>甲方(用人单位):</strong>{{party_a_name}}</p>
<p><strong>法定代表人:</strong>{{party_a_legal_person}}</p>
<p><strong>地址:</strong>{{party_a_address}}</p>
<p><br></p>
<p><strong>乙方(劳动者):</strong>{{party_b_name}}</p>
<p><strong>身份证号码:</strong>{{employee_id_card}}</p>
<p><strong>联系电话:</strong>{{party_b_phone}}</p>
<p><br></p>
<p>根据《中华人民共和国劳动法》、《中华人民共和国劳动合同法》及相关法律法规,甲乙双方本着平等自愿、协商一致的原则,签订本劳动合同。</p>
<p><br></p>
<h3>第一条 合同期限</h3>
<p>本合同为{{contract_term_type}}期限劳动合同。</p>
<p>合同期限自{{start_date}}起至{{end_date}}止。</p>
<p><br></p>
<h3>第二条 工作内容和工作地点</h3>
<p>1. 乙方同意根据甲方工作需要,担任{{job_position}}岗位工作。</p>
<p>2. 工作地点:{{work_location}}</p>
<p><br></p>
<h3>第三条 工作时间和休息休假</h3>
<p>甲方安排乙方执行标准工时制度。</p>
<p><br></p>
<h3>第四条 劳动报酬</h3>
<p>乙方月工资为人民币{{monthly_salary}}元。</p>
<p><br></p>
<h3>第五条 社会保险和福利待遇</h3>
<p>甲方依法为乙方缴纳社会保险费,乙方应缴纳的部分由甲方从乙方工资中代扣代缴。</p>
<p><br></p>
<h3>第六条 劳动保护和劳动条件</h3>
<p>甲方为乙方提供符合国家规定的劳动安全卫生条件和必要的劳动防护用品。</p>
<p><br></p>
<h3>第七条 合同的变更、解除和终止</h3>
<p>按照《劳动合同法》相关规定执行。</p>
<p><br></p>
<p>本合同一式两份,甲乙双方各执一份,自双方签字盖章之日起生效。</p>
<p><br></p>
<p>签订日期:{{sign_date}}</p>`,
minHeight: 100,
padding: 8,
showBorder: false,
},
},
{
id: 'sig-a',
type: 'signature-zone',
props: { width: 200, height: 80, label: '甲方(盖章)' },
signature: {
partyType: 'party_a',
partyLabel: '甲方',
showDate: true,
showSeal: false,
required: true,
},
},
{
id: 'sig-b',
type: 'signature-zone',
props: { width: 200, height: 80, label: '乙方(签名)' },
signature: {
partyType: 'party_b',
partyLabel: '乙方',
showDate: true,
showSeal: false,
required: true,
},
},
],
variables: [
{
code: 'employee_id_card',
name: '员工身份证号',
type: 'text',
required: true,
},
{
code: 'contract_term_type',
name: '合同期限类型',
type: 'text',
required: true,
},
{
code: 'job_position',
name: '工作岗位',
type: 'text',
required: true,
},
{
code: 'work_location',
name: '工作地点',
type: 'text',
required: true,
},
{
code: 'monthly_salary',
name: '月工资',
type: 'money',
required: true,
},
],
parties: [
{ type: 'party_a', label: '甲方(用人单位)', required: true },
{ type: 'party_b', label: '乙方(劳动者)', required: true },
],
},
},
{
id: 'nda',
name: '保密协议',
description: '员工保密协议模板',
category: 'hr',
config: {
id: 'nda-template',
name: '保密协议',
code: 'NDA_CONTRACT',
category: '人事合同',
description: '员工保密协议模板',
pageSize: 'A4',
pageMargin: { top: 60, right: 60, bottom: 60, left: 60 },
elements: [
{
id: 'title-1',
type: 'title',
props: {
content: '保密协议',
level: 1,
align: 'center',
fontSize: 24,
fontWeight: 'bold',
},
},
{
id: 'rich-1',
type: 'rich-text',
props: {
content: `<p style="text-align: right;">协议编号:{{contract_no}}</p>
<p><br></p>
<p><strong>甲方:</strong>{{party_a_name}}</p>
<p><strong>地址:</strong>{{party_a_address}}</p>
<p><br></p>
<p><strong>乙方:</strong>{{party_b_name}}</p>
<p><strong>身份证号:</strong>{{employee_id_card}}</p>
<p><br></p>
<p>鉴于乙方在甲方任职期间,将接触或知悉甲方的商业秘密和保密信息,为保护甲方的合法权益,双方本着平等自愿、诚实信用的原则,签订本协议。</p>
<p><br></p>
<h3>第一条 保密信息的范围</h3>
<p>本协议所称保密信息包括但不限于:技术信息、经营信息及甲方明确要求保密的其他信息。</p>
<p><br></p>
<h3>第二条 保密义务</h3>
<p>1. 乙方应对保密信息严格保密,未经甲方书面同意,不得向任何第三方披露。</p>
<p>2. 乙方不得将保密信息用于本职工作以外的任何目的。</p>
<p>3. 乙方离职时,应将所有保密信息及载体归还甲方。</p>
<p><br></p>
<h3>第三条 保密期限</h3>
<p>乙方的保密义务自本协议签订之日起生效,保密期限为{{confidentiality_years}}年。</p>
<p><br></p>
<h3>第四条 违约责任</h3>
<p>乙方违反本协议约定的,应向甲方支付违约金人民币{{penalty_amount}}元,并赔偿甲方因此遭受的全部损失。</p>
<p><br></p>
<p>本协议一式两份,甲乙双方各执一份,自双方签字盖章之日起生效。</p>
<p><br></p>
<p>签订日期:{{sign_date}}</p>`,
minHeight: 100,
padding: 8,
showBorder: false,
},
},
{
id: 'sig-a',
type: 'signature-zone',
props: { width: 200, height: 80, label: '甲方(盖章)' },
signature: {
partyType: 'party_a',
partyLabel: '甲方',
showDate: true,
showSeal: false,
required: true,
},
},
{
id: 'sig-b',
type: 'signature-zone',
props: { width: 200, height: 80, label: '乙方(签名)' },
signature: {
partyType: 'party_b',
partyLabel: '乙方',
showDate: true,
showSeal: false,
required: true,
},
},
],
variables: [
{
code: 'employee_id_card',
name: '员工身份证号',
type: 'text',
required: true,
},
{
code: 'confidentiality_years',
name: '保密期限(年)',
type: 'number',
required: true,
},
{
code: 'penalty_amount',
name: '违约金金额',
type: 'money',
required: true,
},
],
parties: [
{ type: 'party_a', label: '甲方', required: true },
{ type: 'party_b', label: '乙方', required: true },
],
},
},
{
id: 'service',
name: '服务合同',
description: '通用服务合同模板',
category: 'business',
config: {
id: 'service-template',
name: '服务合同',
code: 'SERVICE_CONTRACT',
category: '商务合同',
description: '通用服务合同模板',
pageSize: 'A4',
pageMargin: { top: 60, right: 60, bottom: 60, left: 60 },
elements: [
{
id: 'title-1',
type: 'title',
props: {
content: '服务合同',
level: 1,
align: 'center',
fontSize: 24,
fontWeight: 'bold',
},
},
{
id: 'rich-1',
type: 'rich-text',
props: {
content: `<p style="text-align: right;">合同编号:{{contract_no}}</p>
<p><br></p>
<p><strong>甲方(委托方):</strong>{{party_a_name}}</p>
<p><strong>地址:</strong>{{party_a_address}}</p>
<p><strong>联系电话:</strong>{{party_a_phone}}</p>
<p><br></p>
<p><strong>乙方(服务方):</strong>{{party_b_name}}</p>
<p><strong>地址:</strong>{{party_b_address}}</p>
<p><strong>联系电话:</strong>{{party_b_phone}}</p>
<p><br></p>
<p>根据《中华人民共和国民法典》及相关法律法规,甲乙双方本着平等互利、诚实信用的原则,就甲方委托乙方提供服务事宜达成如下协议:</p>
<p><br></p>
<h3>第一条 服务内容</h3>
<p>乙方为甲方提供以下服务:{{service_content}}</p>
<p><br></p>
<h3>第二条 服务期限</h3>
<p>服务期限自{{start_date}}起至{{end_date}}止。</p>
<p><br></p>
<h3>第三条 服务费用及支付方式</h3>
<p>1. 服务费用总计人民币{{contract_amount}}元。</p>
<p>2. 支付方式:{{payment_method}}</p>
<p><br></p>
<h3>第四条 双方权利义务</h3>
<p><strong>甲方权利义务:</strong>按时支付服务费用;提供乙方完成服务所需的必要配合。</p>
<p><strong>乙方权利义务:</strong>按照约定提供服务;保证服务质量符合约定标准。</p>
<p><br></p>
<h3>第五条 违约责任</h3>
<p>任何一方违反本合同约定,应承担违约责任,赔偿对方因此遭受的损失。</p>
<p><br></p>
<h3>第六条 争议解决</h3>
<p>本合同在履行过程中发生争议,双方应协商解决;协商不成的,可向甲方所在地人民法院提起诉讼。</p>
<p><br></p>
<p>本合同一式两份,甲乙双方各执一份,自双方签字盖章之日起生效。</p>
<p><br></p>
<p>签订日期:{{sign_date}}</p>`,
minHeight: 100,
padding: 8,
showBorder: false,
},
},
{
id: 'sig-a',
type: 'signature-zone',
props: { width: 200, height: 80, label: '甲方签章' },
signature: {
partyType: 'party_a',
partyLabel: '甲方',
showDate: true,
showSeal: false,
required: true,
},
},
{
id: 'sig-b',
type: 'signature-zone',
props: { width: 200, height: 80, label: '乙方签章' },
signature: {
partyType: 'party_b',
partyLabel: '乙方',
showDate: true,
showSeal: false,
required: true,
},
},
],
variables: [
{
code: 'service_content',
name: '服务内容',
type: 'text',
required: true,
},
{
code: 'payment_method',
name: '支付方式',
type: 'text',
required: true,
},
],
parties: [
{ type: 'party_a', label: '甲方(委托方)', required: true },
{ type: 'party_b', label: '乙方(服务方)', required: true },
],
},
},
{
id: 'rental',
name: '租赁合同',
description: '房屋/设备租赁合同模板',
category: 'business',
config: {
id: 'rental-template',
name: '租赁合同',
code: 'RENTAL_CONTRACT',
category: '商务合同',
description: '租赁合同模板',
pageSize: 'A4',
pageMargin: { top: 60, right: 60, bottom: 60, left: 60 },
elements: [
{
id: 'title-1',
type: 'title',
props: {
content: '租赁合同',
level: 1,
align: 'center',
fontSize: 24,
fontWeight: 'bold',
},
},
{
id: 'rich-1',
type: 'rich-text',
props: {
content: `<p style="text-align: right;">合同编号:{{contract_no}}</p>
<p><br></p>
<p><strong>出租方(甲方):</strong>{{party_a_name}}</p>
<p><strong>联系电话:</strong>{{party_a_phone}}</p>
<p><br></p>
<p><strong>承租方(乙方):</strong>{{party_b_name}}</p>
<p><strong>联系电话:</strong>{{party_b_phone}}</p>
<p><br></p>
<p>根据《中华人民共和国民法典》及相关法律法规,甲乙双方在平等、自愿的基础上,就租赁事宜达成如下协议:</p>
<p><br></p>
<h3>第一条 租赁物</h3>
<p>甲方将位于{{rental_location}}的{{rental_property}}出租给乙方使用。</p>
<p><br></p>
<h3>第二条 租赁期限</h3>
<p>租赁期限自{{start_date}}起至{{end_date}}止。</p>
<p><br></p>
<h3>第三条 租金及支付方式</h3>
<p>1. 租金为人民币{{monthly_rent}}元/月。</p>
<p>2. 支付方式:{{payment_cycle}}</p>
<p><br></p>
<h3>第四条 押金</h3>
<p>乙方应于签订本合同时向甲方支付押金人民币{{deposit_amount}}元。租赁期满,乙方无违约行为且租赁物完好的,甲方应在15日内退还押金。</p>
<p><br></p>
<h3>第五条 租赁物的使用和维护</h3>
<p>1. 乙方应按约定用途使用租赁物,不得擅自改变用途。</p>
<p>2. 乙方应妥善保管和使用租赁物,如有损坏应照价赔偿。</p>
<p><br></p>
<h3>第六条 违约责任</h3>
<p>任何一方违反本合同约定,应承担违约责任,赔偿对方因此遭受的损失。</p>
<p><br></p>
<p>本合同一式两份,甲乙双方各执一份,自双方签字盖章之日起生效。</p>
<p><br></p>
<p>签订日期:{{sign_date}}</p>`,
minHeight: 100,
padding: 8,
showBorder: false,
},
},
{
id: 'sig-a',
type: 'signature-zone',
props: { width: 200, height: 80, label: '甲方(出租方)' },
signature: {
partyType: 'party_a',
partyLabel: '甲方',
showDate: true,
showSeal: false,
required: true,
},
},
{
id: 'sig-b',
type: 'signature-zone',
props: { width: 200, height: 80, label: '乙方(承租方)' },
signature: {
partyType: 'party_b',
partyLabel: '乙方',
showDate: true,
showSeal: false,
required: true,
},
},
],
variables: [
{
code: 'rental_location',
name: '租赁物地址',
type: 'text',
required: true,
},
{
code: 'rental_property',
name: '租赁物名称',
type: 'text',
required: true,
},
{ code: 'monthly_rent', name: '月租金', type: 'money', required: true },
{
code: 'payment_cycle',
name: '支付周期',
type: 'text',
required: true,
},
{
code: 'deposit_amount',
name: '押金金额',
type: 'money',
required: true,
},
],
parties: [
{ type: 'party_a', label: '甲方(出租方)', required: true },
{ type: 'party_b', label: '乙方(承租方)', required: true },
],
},
},
];
// 按分类过滤模板
const getTemplatesByCategory = (category: string) => {
return sampleTemplates.filter((t) => t.category === category);
};
// 加载模板
const loadTemplate = (template: TemplateItem) => {
// 深拷贝配置,避免修改原始数据
const config = JSON.parse(JSON.stringify(template.config));
store.importConfig(JSON.stringify(config));
ElMessage.success(`已加载模板:${template.name}`);
activeTab.value = 'elements';
};
</script>
<template>
<div
class="material-panel flex h-full w-64 flex-col rounded border border-[var(--el-border-color)] bg-[var(--el-bg-color)]"
>
<!-- Tab 切换 -->
<ElTabs v-model="activeTab" class="material-tabs flex-1">
<!-- 合同元素 Tab -->
<ElTabPane
:label="$t('contract-design.contractElements')"
name="elements"
class="h-full"
>
<div class="flex h-full flex-col">
<!-- 搜索框 -->
<div class="flex-shrink-0 p-3">
<ElInput
v-model="searchKeyword"
:placeholder="$t('contract-design.searchElements')"
size="small"
clearable
:prefix-icon="Search"
/>
</div>
<!-- 元素列表 -->
<ElScrollbar class="flex-1">
<div class="px-3 pb-3">
<template
v-for="(materials, category) in filteredMaterials"
:key="category"
>
<div v-if="materials.length > 0" class="component-group mb-4">
<!-- 分组标题 -->
<div
class="group-title mb-2 flex cursor-pointer select-none items-center justify-between text-xs text-[var(--el-text-color-regular)] hover:text-[var(--el-color-primary)]"
@click="toggleGroup(category)"
>
<span class="font-bold">{{ getGroupLabel(category) }}</span>
<ElIcon
class="h-4 w-4 transition-transform"
:class="{
'rotate-180': !activeGroups.includes(category),
}"
>
<Minus />
</ElIcon>
</div>
<!-- 元素网格 -->
<div v-show="activeGroups.includes(category)">
<draggable
:list="materials"
:group="{
name: 'contract-design',
pull: 'clone',
put: false,
}"
:sort="false"
:clone="cloneMaterial"
item-key="type"
class="grid grid-cols-2 gap-2"
@start="onDragStart"
@end="onDragEnd"
>
<template #item="{ element }">
<div
class="component-item flex cursor-move flex-col items-center justify-center rounded border border-[var(--el-border-color)] bg-[var(--el-fill-color-light)] p-3 transition-colors hover:border-[var(--el-color-primary)] hover:text-[var(--el-color-primary)]"
@click.stop="handleClickAdd(element)"
>
<ElIcon class="mb-1.5" :size="20">
<component :is="getIcon(element.icon)" />
</ElIcon>
<span class="text-xs">{{
getMaterialTitle(element.type)
}}</span>
</div>
</template>
</draggable>
</div>
</div>
</template>
</div>
</ElScrollbar>
<!-- 底部提示 -->
<div
class="flex-shrink-0 border-t border-[var(--el-border-color)] px-3 py-2"
>
<div
class="text-center text-xs text-[var(--el-text-color-placeholder)]"
>
<List class="mr-1 inline-block h-3 w-3" />
{{ $t('contract-design.dragOrClickAddElements') }}
</div>
</div>
</div>
</ElTabPane>
<!-- 示例模板 Tab -->
<ElTabPane
:label="$t('contract-design.sampleTemplates')"
name="templates"
class="h-full"
>
<ElScrollbar class="h-full">
<div class="p-3">
<div class="mb-3 text-xs text-[var(--el-text-color-secondary)]">
{{ $t('contract-design.selectTemplateToStart') }}
</div>
<template v-for="cat in templateCategories" :key="cat.key">
<div class="mb-4">
<div
class="mb-2 text-xs font-bold text-[var(--el-text-color-primary)]"
>
{{ cat.label }}
</div>
<div class="space-y-2">
<div
v-for="template in getTemplatesByCategory(cat.key)"
:key="template.id"
class="template-item cursor-pointer rounded border border-[var(--el-border-color)] p-3 transition-all hover:border-[var(--el-color-primary)] hover:bg-[var(--el-color-primary-light-9)]"
@click="loadTemplate(template)"
>
<div
class="mb-1 text-sm font-medium text-[var(--el-text-color-primary)]"
>
{{ template.name }}
</div>
<div class="text-xs text-[var(--el-text-color-secondary)]">
{{ template.description }}
</div>
</div>
</div>
</div>
</template>
</div>
</ElScrollbar>
</ElTabPane>
</ElTabs>
</div>
</template>
<style scoped>
.component-item {
min-height: 60px;
}
.material-panel {
overflow: hidden;
}
.material-tabs {
display: flex;
flex-direction: column;
height: 100%;
}
:deep(.el-tabs__header) {
padding: 0 12px;
margin: 0;
border-bottom: 1px solid var(--el-border-color);
}
:deep(.el-tabs__content) {
flex: 1;
height: 0;
overflow: hidden;
}
:deep(.el-tab-pane) {
height: 100%;
}
.template-item:active {
transform: scale(0.98);
}
</style>